《Windows游戏编程大师技巧》学习笔记(三)
实用的Windows应用程序概要
Windows程序的关键是打开窗口,并对窗口进行文本、图像的显示工作,以及对来自窗口的交互消息做出响应,步骤如下:
- 创建一个Windows类;
- 创建一个事件句柄或WinProc回调函数;
- 注册先前创建的Windows类到Windows系统中;
- 用注册过的Windows类创建一个窗口;
- 创建一个能从事件句柄获取事件或向事件句柄传递Windows信息的事件循环。
Windows类
每一个应用程序至少需要创建一个Windows类,用于描述窗口信息;
描述Windows类信息的数据结构有两个,WNDCLASS
和WNDCLASSEX
,最好选用较新的扩展版本WNDCLASSEX
;
在Unicode环境下,WNDCLASSEXW
被定义成了WNDCLASSEX
,相关定义如下:
1 | typedef struct tagWNDCLASSEXW { |
下面分别对每个字段进行介绍:
UINT cbSize
:记录WNDCLASSEX
结构体本身大小,用于帮助结构体作为指针传递时仅根据第一个字段获取整个结构体大小,一般情况只需要赋值为sizeof(WNDCLASSEX)
;UINT style
:描述窗口属性样式,支持逻辑或运算,常用标志如下:
WNDPROC lpfnWndProc
:窗口事件回调函数,使用方法和回调过程如下,更多内容见后文详述:1
2LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
winclass.lpfnWndProc = WindowProc;int cbClsExtra
和int cbWndExtra
:指示Windows将附加的运行时间信息保存到Windows类某些单元中,大多数情况下可以直接设置为0;HINSTANCE hInstance
:应用程序实例句柄,即WinMain
函数中的HINSTANCE hinstance
参数;HICON hIcon
和HCURSOR hCursor
:应用程序的图标句柄和光标句柄,可以分别通过LoadIcon
和LoadCursor
进行加载,这两个函数在Unicode环境下的原型如下:1
2HICON WINAPI LoadIconW(HINSTANCE hInstance, LPCWSTR lpIconName);
HCURSOR WINAPI LoadCursorW(HINSTANCE hInstance, LPCWSTR lpCursorName);第一个参数表示应用程序实例,函数会从这个应用程序的资源数据中加载指定名称的自定义图标/光标,设置为
NULL
表示从系统资源中加载光标,支持的系统资源名称分别如下:LoadIcon()
的取值:
LoadCursor()
的取值:
HBRUSH hbrBackground
:需要绘制和刷新窗口时背景填充颜色和样式,即“画刷”,与前述图标和光标资源加载类似,函数原型和画刷标识符如下:1
WINGDIAPI HGDIOBJ WINAPI GetStockObject(int i);
LPCWSTR lpszMenuName
:描述菜单资源名称,用于加载和选用窗口,后续深入讨论,可以设置为NULL
;LPCWSTR lpszClassName
:Windows类名称,相当于ID,用于Windows系统标识和识别此Windows类,后续在完成对此类的注册后,可以使用该名称索引此类;HICON hIconSm
:WNDCLASSEX
中新增功能,用于指向窗口标题栏和桌面任务栏的图标句柄,同样可以使用LoadIcon()
进行相关资源的加载。
最后需要将定义好的类注册到系统中:
1 | // WNDCLASSEX winclass; |
创建窗口
使用CreateWindow()
或CreateWindowEx()
,在Unicode环境下,CreateWindowExW()
被定义为CreateWindowEx()
,函数原型如下:
1 | HWND WINAPI CreateWindowExW( |
下面分别对每个参数进行介绍:
DWORD dwExStyle
:窗口扩展样式为高级特效,大多数情况下可以设置为NULL
,常用的值如WS_EX_TOPMOST
可以将窗口始终置于最顶层,其他值见Win32 SDK 帮助或CreateWindowEx 百度百科;LPCWSTR lpClassName
:前述注册的Windows类名;LPCWSTR lpWindowName
:窗口标题;DWORD dwStyle
:窗口样式,支持逻辑或运算,可选值如下:int X
,int Y
,int nWidth
和int nHeight
:分别描述窗口左上角在屏幕坐标系下的像素坐标和窗口的像素宽高,都可以设置为CW_USEDEFAULT
来让系统决定其数值;HWND hWndParent
:存在父窗口的情况下填写父窗口句柄,否则可以设置为NULL
;HMENU hMenu
:窗口菜单句柄,后续深入讨论;HINSTANCE hInstance
:应用程序实例句柄,即WinMain
函数中的HINSTANCE hinstance
参数;LPVOID lpParam
:高级特性,暂设为NULL
。
CreateWindowExW()
函数执行成功将返回窗口句柄,否则返回NULL
;
如果在dwStyle
中没有设置WS_VISIBLE
,那么可以使用如下函数手动显示窗口:
1 | ShowWindow(hwnd, ncmdshow); |
其中hwnd
即函数返回的窗口句柄,ncmdshow
即WinMain
函数中的int hinstance
参数;
通过调用UpdateWindow()
函数来产生WM_PAINT
消息刷新窗口;
事件句柄
前述的WNDPROC lpfnWndProc;
原型如下:
1 | typedef LRESULT (CALLBACK* WNDPROC)( |
下面分别对每个参数进行介绍:
HWND hwnd
:只有在建立多个窗口时才用到,可以用来标识消息来自于哪一个窗口,如下图所示:
UINT msg
:可能是众多消息中的一个,可以配合switch
进行处理,常见的消息说明符如下:
WPARAM wparam
和LPARAM lparam
:用于存储消息类型之外的更多消息数据。
回调函数返回0通知Windows已经处理完成当前事件,无需更多操作,对于未处理的事件,可以通过DefWindowProc()
函数使用默认的方法处理,此函数的参数与回调函数的参数相同;
一个可能的事件回调函数如下:
1 | LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) |
使用BeginPaint()
和EndPaint()
函数来激活窗口的客户区,并使用先前定义的hbrBackground
画刷来填充背景;
其中PAINTSTRUCT ps;
存储了重画的矩形区域数据,相关定义如下:
1 | typedef struct tagPAINTSTRUCT { |
更多内容后续深入讨论,此时只需关注RECT rcPaint;
,它代表的是最小需重画区域的句型结构,相关定义和示意图如下:
1 | typedef struct tagRECT |
BeginPaint()
返回值为指向描述视频系统和正在绘制表面的数据结构句柄;
注意:WM_DESTROY
消息产生于用户关闭窗口时,而非应用程序关闭时,多窗口程序在任一窗口关闭时都将产生WM_DESTROY
消息,但是只有所有窗口都被关闭时才会产生WM_QUIT
消息,这时可以手动调用函数PostQuitMessage()
函数来产生此应用程序退出消息
事件循环
使用GetMessage()
函数或PeekMessage()
函数都可以获取当前事件类型,随后通过调用DispatchMessage()
函数调用先前定义好的事件回调函数进行处理,在Unicode环境下相关函数原型如下:
1 | BOOL WINAPI GetMessageW( |
MSG
结构体的定义如下:
1 | typedef struct tagMSG { |
两种获取事件函数唯一的区别在于GetMessage()
只有发生事件时才会返回,而PeekMessage()
不会阻塞程序,发生事件时返回1,否则返回0;
对于PeekMessage()
最后一个参数,则表示是否将获取到的消息移除事件队列,PM_REMOVE
为移除,PM_NOREMOVE
为不移除;如果选择不移除,可以稍后配合GetMessage()
函数获取并移除消息,以下两种代码是等价的(一般使用前者):
1 | if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) |
完整代码
关于一个实用的Windows应用程序框架,完整的代码如下:
1 |
|
《Windows游戏编程大师技巧》学习笔记(三)