Monday, March 30, 2009

Windows编程 WIN32 API入门三 (MESSAGE, STYLE 和 GDI) (预稿)

前面写的两篇介绍了Windows API编程中最基础的部分. 其中第二篇中的概念比较多, 也比较难理解, 我迟迟没有修改第二篇, 是因为它的确很难修改, 而且修改了也很难说清一些东西, 只能先靠大家悟性去理解了.

这星期都忙于bin32的三个软件的开发, 周末了, 终于有空来写第三篇. 在这一篇里, 主要展示下Window的常见消息.
我会通过一个自定义的Window Class来说明, 这个class 就是 control_spliter, 其源码可以在下面的地址下载http://www.bin32.com/bbs/viewthread.php?tid=47.

集成control_spliter

还记得上一篇里面的Win32 Basic工程么? 我们在这里, 建立一个相似的工程, 名字叫Sample_Spliter, 接下来我们把control_spliter的源码放进去, 然后在Sample_Spliter.cpp的头部加上
#include "control_Spliter.h"
把stdafx.h里面的#define WIN32_LEAN_AND_MEAN删除.
然后在control_Spliter .cpp 头部加上#include "stdafx.h"

这样整个工程就可以顺利编译了.
但是编译了之后什么都没有变化, 是的, 因为我们什么都没有创建.
我们先在Sample_Spliter.cpp上部声明几个全局变量
HWND HWndEdit1;
HWND HWndEdit2;
HWND hWndSpliter;
然后在WndProc这个函数的主switch语句里面加入两个case;


case WM_CREATE: {
initial_Control_Spliter(hInst);
HWndEdit1 = CreateWindow(TEXT("edit"), TEXT("EDIT1"), WS_CHILD|WS_VISIBLE|ES_MULTILINE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);
HWndEdit2 = CreateWindow(TEXT("edit"), TEXT("EDIT2"), WS_CHILD|WS_VISIBLE|ES_MULTILINE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);

CONTROL_SPLITER_INFO Info;
Info.Horizenter = TRUE;
Info.hWndFirst = HWndEdit1;
Info.hWndSecond = HWndEdit2;
Info.SpliterWidth = 4;
Info.Style = 0;
Info.FirstLength = 0;
Info.SecondLength = 0;
Info.Ratio = 1;
Info.CommonLength = 0;
hWndSpliter = CreateWindow(WC_SPLITER, TEXT(""), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, hInst, &Info);

} break;
case WM_SIZE: {
WORD X = LOWORD(lParam);
WORD Y = HIWORD(lParam);
MoveWindow(hWndSpliter, 0, 0, X, Y , TRUE);
} break;



我来简单解释下这两个消息:


WM_CREATE:正像它名字说解释的, 表示窗口正在创建的消息.


我们在这个消息里使用CreateWindow创建了两个EDIT, 然后再创建了一个Spliter.


WM_SIZE:表示窗口被重新调整了大小, 从代码上看, 貌似我们通过lParam取得了窗口了X值和Y值, 然后MoveWindow应该是把Spliter移动在0,0点, 宽为X, 高为Y. 是的, 事实完全就是这样, 当窗口接受到WM_SIZE消息时, lParam(它是一个DWORD)就包含了窗体”客户区”的高度和宽度值, MoveWindow可以用来移动窗口, 这里被移动的是子窗口, 将它移动到完全覆盖父窗体的”客户区”的位置.


来我们看看效果~



哎呀, 中间一个小棍棍把两个缺钙的Edit分开了, 鼠标点点, 中间的小棍棍还能动~ (至于这两个小Edit为什么缺钙, 以后再说)




常见WINDOW STYLE



你可能已经发现, 我们在使用CreateWindow 函数时, 第三个参数都包含, WS_CHILD | WS_VISIBLE.

CreateWindow的第三个参数是Window的STYLE (“WS_”):


WS_CHILD:


表示这是一个子窗体, 此时CreateWindow倒数第四个参数表示这个Window的父窗体, 如果父窗体是NULL, 函数会失败.


WS_VISIBLE:


表示窗体是否可见, 大多数时候我们创建Window时并不需要所有Window都显示, 比如播放器的播放列表.


另外创建Edit的时候还使用了ES_MULTILINE:


这是一个Edit专用的STYLE,(“ES_”) 它表示这个Edit可以接受多行输入.


|:


或操作符 (如果你已经忘了), &, ^, ~和|在这里使用非常广泛. 毕竟你需要把一堆信息通过一个参数传进去. 如果你仍然不理解这个, 你可能需要仔细回顾位操作的用途.




接下来我给出几个非常常见的通用Window STYLE, 具体的可以查阅MSDN, 专用STYLE也因该查阅MSDN.


WS_CHILD, WS_VISIBLE: 最常用的两个STYLE, 已经说过了.


WS_ OVERLAPPEDWINDOW: 独立窗口的典型STYLE.


WS_BORDER: 带有边框的, 通常那边框可能不是你想像的那么好看.


WS_DISABLED: 不可使用, 通常表现为”灰掉”.


WS_VSCROLL, WS_HSCROLL: 水平滚轮和上线滚轮, 不过通常这个滚轮不太好控制.




Extended Style

至今我们都在用CreateWindow创建窗口, 其实我们还可以使用CreateWindowEx, 两者的差别只是CreateWindowEx多了一个参数: Extended Style, Extended Style很常用, 我简单说说常用的WS_EX_:


WS_EX_ACCEPTFILES: 原来下载软件的文件拖拽的窗口就是用的它~.

WS_EX_LAYERED: 你是否觉得那些程序的半透明的或者渐变透明的窗口都很神奇? 其实他们只是使用了layered窗口的特性而已. 不过layered窗口使用其他会不太一样, 使用这个属性之前, 要好好研究下layered窗口才行.


WS_EX_TOOLWINDOW: 你是否觉得你的程序的标题边框太大, 这个属性可以帮你解决这个问题. 不过这个属性的实际用途是提供一个能够浮动的小工具栏窗口.


WS_EX_TOPMOST: 置顶窗口. 想挡住别人, 用它吧~




Spliter的实现


举完这么多STYLE, 我们还是回到消息上, 我们来自习看看我们的Spliter.


它能够接收鼠标按下, 和拖动的消息. 拖动的时候自动把两个小窗体大小调整, 我们来看看control_Spliter里面有什么.


VOID Redraw(…), 貌似是一个重绘函数.


LRESULT CALLBACK proc_Control_Spliter(…), 消息处理循环, 应该是处理spliter这个Window的消息的.


BOOL initial_Control_Spliter(…), 初始化函数.


initial_Control_Spliter很简单, 注册了一个Window的class, 我们知道, Window必须选注册才能使用, 所以我们得先运行这个函数.


Redraw是一个利用DC重绘的函数, 也就是传说的GDI绘图.




GDI



GDI是什么, 怎么实现了的. 这不重要, 对我们来说重要的如何使用它, 我现在给出一个可能让你觉得很失望的解释.

GDI绘图就是使用一堆普通的C/C++函数, 唯一有点区别的就是被绘图的对象是一个特殊句柄HDC.


我们来看看Redraw这个函数, 它有两个参数, HDC和CONTROL_SPLITER_INFO, CONTROL_SPLITER_INFO是一个特殊的自定义结构, 它在control_Splite.h里面定义, 它用来保存这个Spliter的信息.


VOID Redraw(HDC hdc, CONTROL_SPLITER_INFO * SpliterInfo ) {


    RECT Temp;//这是一个特殊的结构, 它有4个LONG值, 分别表示一个矩形的左上点和右下点的X,Y值.


    // 一堆计算操作


    FillRect(hdc, &Temp,CreateSolidBrush(RGB(240, 240, 240)));


    // 填充一个矩形, 位置在Temp里, 颜色是RGB:240, 240, 240


    // 又是一堆计算操作


    FrameRect(hdc, &Temp,CreateSolidBrush(RGB(180, 180, 180)));


    // 画一个矩形的边框, 位置在Temp里, 颜色是RGB:180, 180, 180


}





你可能已经猜出来了, 那两个Edit中间的小棍棍是利用两个矩形画出来的. 是的, 这就是传说中的GDI. 那接下来一个问题, 那个HDC是哪里来的呢?




其实只要介绍两个函数:


GetDC(HWND) :取得一个窗口的HDC, 得到的HDC是这个窗口的”客户区”, 这个名词之前有提到, 它表示这个窗口除了边框外, 中间的区域.


取得这个HDC之后, 就可以在这个HDC里面使用GDI函数画图了.


ReleaseDC(HWND, HDC) :事实上, 里面使用GDI函数在HDC上画图, 实际被画图的内容并没有被显示出来, 你需要调用ReleaseDC这个单数让被绘制的效果立即显示, 如果你不调用这个函数, 绘制的效果会过一段时间后显示, 不过这可能会引起一些问题.



WM_PAINT事件, 当有这个事件发生时, 你需要调用


BeginPaint(HWND, &PAINTSTRUCT), EndPaint(HWND, &PAINTSTRUCT);这两个函数, 函数中的PAINTSTRUCT结构里面已经有了HDC, 不需要调用GetDC, 和ReleaseDC.



重要的GDI结构:

RECT, POINT
. RECT刚才已经说到, 它表示矩形, POINT表示一个点.


GDI的函数库里面有足够多的函数可以进行对RECT和POINT操作, 如坐标映射, 矩形的交, 并运算, 判断点是否在坐标等等. 你不需要自己写函数对其运算.




重要的GDI功能:




  • 绘制点, 线条, 矩形, 椭圆, 多边形, 弦, 饼图.


  • 输出文字, 输出图片.


  • 图片切片, 变形等处理.


  • 笔刷, 颜色笔刷, 或者图片笔刷等等..



可以说GDI能够做所有的绘图相关的事情, 只是有的简单, 有的麻烦.



常见的消息和鼠标函数



介绍完GDI, 我们再回来看看最后一个函数: proc_Control_Spliter, 我们要做的就是根据这个函数, 介绍下常见的消息和鼠标函数.

WM_CREATE: 我们又看见它了, 它首先调用了malloc分配一块内存,


然后调用了这句话:


*SpliterInfo = *(CONTROL_SPLITER_INFO *)(((CREATESTRUCT *)lParam)->lpCreateParams);


还是否记得我们在WndProc的WM_CERATE里面创建了一个CONTROL_SPLITER_INFO, 然后把它作为CreateWindow的最后一个参数传了进去, 这句话就可以把这个结构弄出来, 并且赋值给* SpliterInfo. 通过这个手段, 我们可以为自己创建的Class提供足够的显示数据.


接下来运行了:


SetWindowLong(hWnd, GWL_USERDATA, (LONG)SpliterInfo);


这句和下面我们会看到的GetWindowLong(hWnd, GWL_USERDATA);是相对应了, 其目的就是把SpliterInfo的地址设为这个窗口的” USERDATA”, 这个”USERDATA”会由操作系统保存, 之后调用GetWindowLong就可以将其取出, 是不是很方便啊~


接下来是两个SetParent, 把那两个Edit设为自己的子窗口.


WM_DESTROY: 窗体要被”扔掉”的消息, 在这个消息里面我们把USERDATA的内存给free掉了.


WM_PAINT: 绘图消息, 当然是使用我们的Redraw函数绘图了.


WM_MOUSEMOVE:鼠标移动的消息, 在这里,我们经过了一系列复杂的运算, 最后调用了两个MoveWindow把那两个Edit放到合适的地方.


最后我们运行了下面这句话:


hdc = GetDC(hWnd);


Redraw(hdc, SpliterInfo);


ReleaseDC(hWnd, hdc);


其实MoveWindow最后一个参数就是是否对窗口进行重绘, 如果为TRUE, 则被Move的窗口会接收到WM_PAINT消息.


但是我们这里还是主动调用了绘图函数, 为什么呢?


消息队列及优先级


这涉及到Window的消息队列问题,每个Window都有自己的消息队列, 记录已经接受到的消息, 但是消息队列里面的消息是有优先级的, 
WM_MOUSEMOVE的优先级远比WM_PAINT高, 换句话说, 如果WM_MOUSEMOVE不断产生, WM_PAINT可能是一直不被发出的. 所以我们这里在WM_MOUSEMOVE里面主动调用了GDI函数.


WM_LBUTTONDOWN, WM_LBUTTONUP: 从字面上看, 是”鼠标左键被按下, 和按起”.


我们在这两个消息中运行了SetCapture和ReleaseCapture两个函数, 前者表示”从现在开始, 鼠标事件一直发生在我身上”, 换句话说, 就算鼠标离开了窗口的范围, WM_MOUSEMOVE消息也会不断产生. ReleaseCapture则解除这个功能.


WM_SIZE: 窗口被重新调整大小消息, 我们这里进行了一堆复杂的操作后, 强制进行了重绘.



其他常见消息:


你可以主动向窗口发送消息, (SendMessage函数).

利用这个函数, 你既可以实现一些通知功能, 也可以实现信息获取功能.下面给出几个非常常用的消息, 你可以从中理解这一点.


WM_CLOSE: 和WM_DESTROY不一样, 这个消息只表示你点击了窗口右上角的叉, 不过默认它会调用WM_DESTROY.


WM_GETTEXT: 取得Window的TEXT, 一般你只需要发送这个消息, 而不需要自己处理这个消息.


WM_ENABLE: 设定Window是否有效, 和GETTEXT一样, 一般不需要自己处理.

No comments:

Post a Comment