1. Windows程序的入口函数WinMain:
int WINAPI WinMain( // App的入口函数,是一个API,由操作系统调用,以下这四个参数都是由操作系统分配并传递进来,当然后两个可以由用户指定 HINSTANCE hInstance, // 当前App的实例号 HINSTANCE hPrevInstance, // Win32里总是为NULL // 当前App的上一个兄弟App的实例号 //(一个程序同时开多个,则各个App按照时间顺序排列,比如打开多个记事本进行编辑) LPSTR lpCmdLine, // char *的传统C串,LP=long pointer,长指针指32位,该App的命令行参数(不包括该程序的名称) // 如果想获得完整的命令行参数(包括命令名)则要使用GetCommandLine函数 int nCmdShow // 宏,通过命令行传参的方式决定改程序的窗口如何被(打开)显示 //(最大化、最小化、上次打开时的大小等,前缀是SW_,即show_window) );2. 创建窗口的四个步骤:
一、设计窗口类:
窗口类类型结构体:
typedef struct _WNDCLASS { // 窗口的类型类,相当于一个类模板,根据该模板创建相应的窗口 UINT style; // 宏,表示窗口的风格,比如水平重刷、垂直重刷等 //(表示如果窗口在水平或垂直方向伸缩时窗口要重画) // 以CS_作为前缀,表示class style WNDPROC lpfnWndProc; // lpfn=long pointer to function,一函数指针 // 表示该种类型窗口收到消息后由该指针指定的函数处理消息 // 即窗口的消息响应函数也称为回调函数,也成为过程函数,其调用约定为CALLBACK // !之后会介绍回调的含义 // 类结构以及窗口结构中预留的一些额外字节空间 int cbClsExtra; // cb=count of byte,Cls=class,一般为0 // 表示给该类型类WNDCLASS结构体指定额外的字节空间以指定额外成员变量 int cbWndExtra; // 以该类型类为模板创建窗口的窗口实例的额外字节空间,一般为0 HINSTANCE hInstance; // 表示创建该窗口的App的实例号,App和窗口可以看成父子线程的关系 HICON hIcon; // 窗口的图标句柄,即窗口左上角的那个图形标识 HCURSOR hCursor; // 窗口的光标句柄,即决定光标进入窗口区域后该如何显示,如正常显示还是显示成一个十字等 HBRUSH hbrBackground; // 窗口的画刷句柄,表示窗口重画时使用何种画刷,如黑色画刷或深灰画刷等 // 画刷只能用于填充用户区域的背景颜色 // !之后会介绍用户区域以及背景颜色的概念 LPCTSTR lpszMenuName; // LPCSTR=const char *常量C串,即窗口顶部第二行菜单栏的名称 LPCTSTR lpszClassName; // sz=const string即长两字符串,该种类型窗口的类名 // 注册窗口以及创建窗口都以类名作为依据 } WNDCLASS, *PWNDCLASS;
一般地,诸如上述涉及到字符串的结构体或者API都会有两个版本,这是一个通用版本,也会有WNDCLASSA和WNDCLASSW两个版本
回调函数:一般用户在程序中都是调用操作系统的函数为其提供服务,但是对于窗口过程函数(消息响应函数)是由操作系统调用的用户定义的函数,即和一般常规的函数调用关系相反(即操作系统调用用户),一般Win32程序开始执行时操作系统会为其分配一个消息队列,该应用程序所接受的所有消息一部分会被放入消息队列中,应用程序会从消息队列中取出消息,然后跳出应用程序代码,由操作系统将消息分发给响应的过程函数(操作系统调用用户定义的过程函数),待过程函数执行完毕后,控制权再交由操作系统,重新回到应用程序代码中,对于其它消息(不会被投递到消息队列中),则会直接被操作系统送到过程函数处进行处理
过程函数可以位于应用程序中也可以位于动态链接库中
窗口的用户区:即client area,是指标题栏和窗口边框之间的区域,用户可以在这个区域自由绘图并向用户传达可视输出区域,其它区域,比如菜单栏等系统定制的区域则不能由用户任意修改,而背景色就是指用户区的背景色,用户可以在背景色基础之上绘出各种各样的图案,特别适当窗口大小改变或被遮盖后重新显示时都需要对窗口进行重绘,而重绘的步骤就是先对重绘区域刷上背景色,然后再在上面复原之前的图案以及信息
窗口类定义了窗口的基本蓝图(模型),即大体的特征,而创建窗口(CreateWindow)时,就是依据该模板创建具体的窗口资源(窗口总是基于窗口类来创建的),在这个阶段才会具体定义窗口的一些细节特征,窗口类定义中最重要的两个特征一个就是指定窗口过程函数还有一个就是窗口类的名字,因为窗口类需要过程函数来处理消息,一个类可定义多个不同细节的窗口,但同一个窗口类定义的窗口共享一个过程函数,而类的名字则是创建具体窗口的唯一依据
创建窗口类类型结构体并定义其中的字段(即类属性):
{ // WinMain函数体 WNDCLASS wndclass; // 窗口类结构 HWND hWnd; // 窗口实例句柄 MSG msg; // 消息结构 wndclass.style = CS_HREDRAW | CS_VREDRAW; // 水平重刷和垂直重刷的组合风格 wndclass.lpfnWndProc = WndProc; // 指定一个消息响应函数 wndclass.cbClsExtra = 0; // 额外空间都指定为0 wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; // 窗口实例的父线程实例号就是创建它的App的实例号,即由WinMain传入 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ); // 加载该种类型窗口的图标 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); // 加载该种类型窗口的光标 wndclass.hbrBackground = (HBRUSH)GetStockObject( GRAY_BRUSH ); // 加载画刷 wndclass.lpszMenuName = NULL; // 这里现不设置菜单名 wndclass.lpszClassName = TEXT("HelloWin"); // 模板类的名称,即类名
HICON LoadIcon( // 当使用系统默认的标准光标时第一个参数传NULL,第二个穿IDI_为前缀的宏 // IDI_表示ID_Icon的意思 HINSTANCE hInstance, // 光标、图标等都是一种资源文件(可执行文件),加载之前得现运行它 // 运行时就是一种线程实例,加载的时候以实例号作为表示,由于资源文件是一种可执行文件 // 所以也必须通过源代码编译后才能形成,编译资源文件的编译器称作资源编译器 LPCTSTR lpIconName // 光标名称 ); // LoadCursor完全相同 HGDIOBJ GetStockObject( //获得系统预定义的(即库存中的)GDI设备(Graphics Device Interface,即图形设备接口),如画刷、字体、调色板等 int fnObject // 宏,指定一种预定义的GDI设备 ); // 注意!返回值是GDI设备的句柄,想使用具体的某一种设备需要对返回值做类型强转
关于若干匈牙利命名法前缀的总结:
c:char WCHAR TCHAR
by:BYTE
n:short
i:int
cb:32位整型,count of byte
x, y:int,表示x和y坐标
cx, cy:int,表示宽度和高度,count of x/y,如果按照像素为单位进行计算则使用count非常表意
B/f:BOOL/flag
w:WORD
l:LONG
dw:DWORD
fn:function
s:字符串
sz:string terminated with a zero
h:句柄
p:指针
结构体变量的前缀即为结构体名的小写(Win32 API中的结构体一般都是全大写的),比如:
WNDCLASS:wndclass
RECT:rect
特殊的,PAINTSTRUCT:ps,一些特别长特别复杂的结构体名称的前缀可以使用小写的缩写形式
二、向操作系统注册窗口类类型:
RegisterClass( &wndclass ); // 将该窗口类注册给操作系统,这样就可以在接下来的代码中创建该种类型的窗口示例了
关于内存不足的问题:注册类型就意味着需要为该类型分配一定的空间在内存中驻留,当空间不足或者lpfnWndProc为NULL时RegisterClass会发生异常返回0
三、创建窗口实例:
HWND CreateWindow( // 创建窗口实例的API LPCTSTR lpClassName, // 注册过的窗口类的类名,以该类型为模板创建窗口实例,非常重要! LPCTSTR lpWindowName, // 窗口名称,即标题栏名称,位于窗口顶部 DWORD dwStyle, // 宏,窗口的风格,以WS_为前缀,表示Window Style,比如有无系统菜单,有无最大化按钮等 int x, // 窗口左上角在屏幕中的坐标,向左为x正,向下为y正 int y, int nWidth, // 窗口的宽和高 int nHeight, HWND hWndParent, // 指示父窗口的窗口句柄,有时一个窗口中有很多小的子窗口,此时打开子窗口时需要指定该参数 HMENU hMenu, // 窗口的菜单资源句柄 HINSTANCE hInstance, // 窗口所属App的实例号 LPVOID lpParam // 指向创建窗口时的额外数据区,一般不用,传NULL );
HWND hWnd = CreateWindow( "测试1", //注册类名,很重要 "欢迎来到测试1", // 标题 WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, // 窗口风格,去掉其中的最大化按钮 CW_USEDEFAULT, CW_USEDEFAULT, // 左上角坐标 CW_USEDEFAULT, CW_USEDEFAULT, // 宽和高 NULL, // 父窗口句柄 NULL, // 菜单资源句柄 hInstance, // 所属App实例号 NULL // 附加数据区 );
CW是指Create Window,这里四个参数使用的都是系统定义的默认值0x80000000,默认情况下(都设为WS_USEDEFAULT的情况下),对于连续创建的多个窗口,新建窗口的左上角位置沿水平方向和垂直方向作步长为1的偏移
关于父窗口句柄:如果新建窗口为顶级窗口(应用程序窗口),则理论上其父窗口就是桌面,但是只需要传NULL就行了,一般来说当两个窗口存在父子关系时子窗口总是位于父窗口的前方
关于窗口风格的选项:
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | // 窗口可重叠 WS_CAPTION | // 具有标题 WS_SYSMENU | // 具有系统菜单,即点击左上角图标可跳出菜单 WS_THICKFRAME | // 具有一个尺寸可以自由调整的外边框 WS_MINIMIZEBOX | // 具有最小化 WS_MAXIMIZEBOX // 具有最大化WS就是指Window Style
使用CreateWindow函数创建窗口后仅仅是创建了窗口资源(即给窗口资源分配了内存),但并不会显示窗口的内容,但不过该函数会自动隐式地产生一条WM_CREATE消息,该消息不会被投递到消息队列中,而是直接发送到WndProc过程函数那里去进行处理(这种消息称为“非队列消息”,此概念之后会阐述),而该消息往往也是过程函数处理的第一条消息,一般对该消息的处理都是进行一些初始化工作,比如这里的程序就是放一段欢迎的音频作为初始化的内容
四、显示以及刷新窗口画面:
BOOL ShowWindow( // 显示窗口,BOOL=int HWND hWnd, // 显示的窗口的句柄 int nCmdShow // 宏,也可以通过WinMain命令行指定,即打开方式 // 以SW_作为前缀,指Show Window,最大化还是最小化还是默认等 );
ShowWindow( hWnd, nCmdShowL ); // 以正常方式显示
该函数产生任何消息,就知直接将窗口在桌面中显示,如果第二个参数指定为SW_SHOWNORMAL,则新显示出的窗口中的用户区会用窗口类中指定的画刷刷一遍(这是是全灰的,没有图案或信息),如果想立即显示出图案和信息则需再调一个函数:
UpdateWindow( hWnd ); // 会产生一条WM_PAINT消息并绕过消息队列直接发送给过程函数让其重绘用户区(即显示信息和图案)
当然也可以选择其它选项SW_SHOWMAXIMIZED(最大化显示)、SW_SHOWMINNOACTIVE(最小化隐藏于任务栏)
关于如何设置命令行参数nCmdShow的值的问题:可以直接右击改程序图标并打开属性对话框,然后按照如图设置即可
3. 通过消息循环接受并分发消息队列中的消息:
消息结构体的定义:
typedef struct tagMSG { // 消息的结构体MSG的定义 HWND hwnd; // 产生该消息的窗口 UINT message; // unsigned int宏,消息的类型,低16位App用,高16位系统保留 WPARAM wParam; // unsigned int值,附加参数,比如message=WM_CHAR(按键消息),但是具体按的是哪个键本参数存储ASCII值 LPARAM lParam; // 同样是附加参数,一般不用,w是指WORD,l是指LONG DWORD time; // unsigned long值,32位,本消息投递的时间 POINT pt; // 结构体,表示消息投递时鼠标在屏幕上的位置(即二维坐标) } MSG, *PMSG;操作系统会将窗口产生的消息(比如在该窗口内的鼠标点击、键盘按键灯消息)包装秤消息结构体投递到消息队列中
而接下来的GetMessage函数就从消息队列头部获取最近的一个消息,前提是要提前创建消息结构体用于接收队列中的消息:
BOOL GetMessage( // 从消息队列的头部获取消息,获取之后头部弹出 // 操作系统会为每个App分配一个消息队列用来接收消息 LPMSG lpMsg, // 将从队列头部获取的消息放入该指针指向的消息结构体中 HWND hWnd, // 表示从哪个窗口获取消息,该窗口必须为当前App中所创建的窗口 // 如果为NULL则表示从当前App中创建的所有窗口获取消息 UINT wMsgFilterMin, // 宏,表示过滤出感兴趣的消息的范围 // 比如WM_KEYFIRST ~ WM_KEYLAST或WM_MOUSEFIRST ~ WM_MOUSELAST UINT wMsgFilterMax // 就表示过滤出两次按键之间的消息或两次点击之间的消息,WM_表示Window Messages // 即窗口消息,如果为0, 0表示不过滤,所有消息都要 ); // 如果函数出现异常(比如hWnd不存在等)返回-1,如果收到WM_QUIT消息返回0,其它消息返回1
接下来就是著名的消息循环,不停地接收窗口产生的消息,并把消息分发给各个过程函数进行处理,处理完成之后就将响应的消息释放,可谓窗口是消息的产生地,而过程函数则是消息的集散地以及处理地,WM_QUIT消息一定是队列消息,因此WM_QUIT消息的投递必定会终止消息循环:
MSG msg; while ( GetMessage( &msg, hWnd, 0, 0 ) ) // 从hWnd窗口处获取消息 { TranslateMessage( &msg ); // 可以将按键消息(由WM_KEYDOWN和WM_KEYUP组合而成) // 转化成一个新的WM_CHAR消息并将按键的ASCII传给msg.wParam DispatchMessage( &msg ); // 将消息分发给相应的过程函数进行处理(发送给msg中hWnd所属的窗口类中指定的过程函数进行处理) // 一个App可能有多个窗口类,但每个窗口类只可以有一个消息响应函数 }注意!如果不使用多线程技术,DispatchMessage函数会等待WndProc执行完毕后才会返回,如果某个消息的响应过程非常长而不用多线程技术的话会非常坑爹的,其它消息就不能被及时处理而卡在那里白白等待!
4. 消息处理——窗口过程:
窗口过程决定了客户区显示的内容以及处理对消息的消息的响应,一个窗口类可以对应多个窗口实例,但是一个窗口类只能对应一个窗口过程
过程函数的名称可以由用户自由决定,但是窗口过程的函数原型必须是库中指定的函数原型,原型中的四个参数就是MSG中的前四个字段,当将消息分发给过程函数的时候操作系统就将消息中的前四个字段拆分传送给过程函数进行处理
一般都是由Windows操作系统自身调用窗口函数,但是用户也可以认为调用,必须同时使用SendMessage函数来实现,一般来说Send指直接向过程函数发送消息,而Post则是向消息队列投递消息,如果函数名中出现这两个单词则一定要注意了!
LRESULT CALLBACK WndProc( // 窗口的消息响应函数,返回值为long类型(L即long的意思) // 根据接受消息的不同返回值会有所不同,由具体处理代码决定 HWND hWnd, // 响应消息的窗口,比如该窗口中通过显示某些文字来响应接收到的消息 UINT uMsg, // 宏,表示消息的类型,以WM_作为前缀 WPARAM wParam, // 消息附加参数,比如按键的ASCII值 LPARAM lParam // 即MSG的前4个参数 ) // DispatchMessage函数自动将收到的消息msg中的值作为响应函数的参数传入 // 这是响应函数的标准接口,必须这样写(API规定),但是名字可以任意取 { HDC hDC; // 设备环境句柄(即GDI设备驱动,包含各种驱动功能,如画刷、文本输出、曲线绘制等等各种功能) PAINTSTRUCT ps; // 重绘结构体,保存窗口过程对客户区进行重绘的一些信息,在响应WM_PAINT消息中专用 RECT rect; // 表示一个矩形区域,可以是系统定义的任何对象(窗口、按钮、边框等等) switch ( uMsg ) // 对于不同类型的消息采取多分支处理,这是一般方式 { case WM_CREATE: // 响应CreateWindow的在内存中创建窗口资源的消息,进行一定的初始化,这里就是播放一段波形音频 // 该消息也是窗口类被注册后所收到的第一条消息 // 专业环境下都是在响应该消息的过程中对窗口作出一次性初始化! // 该函数播放一段音频文件 // 第一个参数指定一段音频文件,有多种方式指定,如文件名形式或资源文件的形式 // 只有当第一个参数为资源文件时第二个参数才有用 // 第三个参数表示播放的形式,SND_FILENAME表示以文件名的形式指定音频(即第一个参数是文件名) // SND_ASYNC表示以异步方式播放,即声音一播放PlaySound函数就返回而无需等待 PlaySound( TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC ); return 0; case WM_PAINT: // 重刷消息的处理,窗口形状大小的改变以及被遮盖部分重新显露等都会触发重刷消息 // !关于重绘消息触发的基本原理会在后面介绍 hDC = BeginPaint( hWnd, &ps ); // 这一步为重绘客户区做准备,同时将GDI驱动设备和具体的窗口相关联 // 这就表示GDI将在hWnd所指定窗口的客户区进行绘图以及信息文本输出 // 首先会将客户区中的无效区域用WNDCLASS中指定的画刷重刷(即重刷背景) // 接下来到EndPaint之前才是重现客户区的具体图案和信息文字等 GetClientRect( hWnd, &rect ); // 获得当前客户区无效矩形区域的信息,将信息保存在rect结构体中 DrawText( hDC, TEXT("Hello Windows!"), -1, &rect, // 绘制文本信息!之后会详细讲解 DT_SINGLELINE | DT_CENTER | DT_VCENTER ); EndPaint( hWnd, &ps ); // 结束重绘,将GDI资源释放 return 0;// !响应WM_PAINT函数必须使用BeginPaint和EndPaint函数! case WM_CLOSE: // 关闭窗口的消息(右上角的关闭按钮被触发) if ( IDYES == MessageBox( hWnd, TEXT("是否要退出程序?"), TEXT("提示"), MB_YESNO ) ) // 如果用户点击的是Yes按钮(即表示确认退出程序) { DestroyWindow( hWnd ); // 先销毁窗口的画面,如果成功则返回一个非0值,否则返回0 // 同时自动产生一个WM_DESTROY消息绕过消息队列直接传给过程函数 } return 0; case WM_DESTROY: // 窗口销毁消息的处理 PostQuitMessage( 0 ); // 产生一个WM_QUIT消息投递到消息队列中去 // 可以终止while循环让其停止并推出WinMain // 参数0作为WM_QUIT消息的附加参数wParam处理 // 一般该参数直接作为WinMain函数的退出码 return 0; } // 对于不感兴趣的消息必须使用该函数进行默认处理,否则会出现异常(无法进行其它正常行为) return DefWindowProc( hWnd, uMsg, wParam, lParam ); // 参数即为WindowProc的四个参数 }
int DrawText( HDC hDC, // 利用获得的hDC进行绘图 LPCTSTR lpString, // 绘制的文本 int nCount, // 文本的长度,如果为-1表示文本以'\0'为结尾 LPRECT lpRect, // 绘制区域(矩形) UINT uFormat // 绘制选项 );
DT_表示DrawText
DT_SINGLELINE表示单行显示
DT_CENTER表示水平居中
DT_VCENTER表示垂直居中
5. 完整的代码(注意,创建工程的时候选择“空工程”即可):
#include <windows.h> // 过程函数的声明 LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); int WINAPI WinMain( // App的入口函数,是一个API,由操作系统调用,以下这四个参数都是由操作系统分配并传递进来,当然后两个可以由用户指定 HINSTANCE hInstance, // 当前App的实例号 HINSTANCE hPrevInstance, // Win32里总是为NULL // 当前App的上一个兄弟App的实例号 //(一个程序同时开多个,则各个App按照时间顺序排列,比如打开多个记事本进行编辑) LPSTR lpCmdLine, // char *的传统C串,LP=long pointer,长指针指32位,该App的命令行参数(不包括该程序的名称) // 如果想获得完整的命令行参数(包括命令名)则要使用GetCommandLine函数 int nCmdShow // 宏,通过命令行传参的方式决定改程序的窗口如何被(打开)显示 //(最大化、最小化、上次打开时的大小等,前缀是SW_,即show_window) ) { // WinMain函数体 WNDCLASS wndclass; // 窗口类结构 HWND hWnd; // 窗口实例句柄 MSG msg; // 消息结构 wndclass.style = CS_HREDRAW | CS_VREDRAW; // 水平重刷和垂直重刷的组合风格 wndclass.lpfnWndProc = WndProc; // 指定一个消息响应函数 wndclass.cbClsExtra = 0; // 额外空间都指定为0 wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; // 窗口实例的父线程实例号就是创建它的App的实例号,即由WinMain传入 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ); // 加载该种类型窗口的图标 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); // 加载该种类型窗口的光标 wndclass.hbrBackground = (HBRUSH)GetStockObject( GRAY_BRUSH ); // 加载画刷 wndclass.lpszMenuName = NULL; // 这里现不设置菜单名 wndclass.lpszClassName = TEXT("HelloWin"); // 模板类的名称,即类名 RegisterClass( &wndclass ); // 将该窗口类注册给操作系统,这样就可以在接下来的代码中创建该种类型的窗口示例了 hWnd = CreateWindow( TEXT("HelloWin"), //注册类名,很重要 TEXT("Hello Windows"), // 标题 WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, // 窗口风格,去掉其中的最大化按钮 CW_USEDEFAULT, CW_USEDEFAULT, // 左上角坐标 CW_USEDEFAULT, CW_USEDEFAULT, // 宽和高 NULL, // 父窗口句柄 NULL, // 菜单资源句柄 hInstance, // 所属App实例号 NULL // 附加数据区 ); ShowWindow( hWnd, nCmdShow ); // 以正常方式显示 UpdateWindow( hWnd ); // 会产生一条WM_PAINT消息并绕过消息队列直接发送给过程函数让其重绘用户区(即显示信息和图案) while ( GetMessage( &msg, hWnd, 0, 0 ) ) // 从hWnd窗口处获取消息 { TranslateMessage( &msg ); // 可以将按键消息(由WM_KEYDOWN和WM_KEYUP组合而成) // 转化成一个新的WM_CHAR消息并将按键的ASCII传给msg.wParam DispatchMessage( &msg ); // 将消息分发给相应的过程函数进行处理(发送给msg中hWnd所属的窗口类中指定的过程函数进行处理) // 一个App可能有多个窗口类,但每个窗口类只可以有一个消息响应函数 } return msg.wParam; // 退出码即为WM_QUIT消息的附加参数,即为0 } LRESULT CALLBACK WndProc( // 窗口的消息响应函数,返回值为long类型(L即long的意思) // 根据接受消息的不同返回值会有所不同,由具体处理代码决定 HWND hWnd, // 响应消息的窗口,比如该窗口中通过显示某些文字来响应接收到的消息 UINT uMsg, // 宏,表示消息的类型,以WM_作为前缀 WPARAM wParam, // 消息附加参数,比如按键的ASCII值 LPARAM lParam // 即MSG的前4个参数 ) // DispatchMessage函数自动将收到的消息msg中的值作为响应函数的参数传入 // 这是响应函数的标准接口,必须这样写(API规定),但是名字可以任意取 { HDC hDC; // 设备环境句柄(即GDI设备驱动,包含各种驱动功能,如画刷、文本输出、曲线绘制等等各种功能) PAINTSTRUCT ps; // 重绘结构体,保存窗口过程对客户区进行重绘的一些信息,在响应WM_PAINT消息中专用 RECT rect; // 表示一个矩形区域,可以是系统定义的任何对象(窗口、按钮、边框等等) switch ( uMsg ) // 对于不同类型的消息采取多分支处理,这是一般方式 { case WM_CREATE: // 响应CreateWindow的在内存中创建窗口资源的消息,进行一定的初始化,这里就是播放一段波形音频 // 该消息也是窗口类被注册后所收到的第一条消息 // 专业环境下都是在响应该消息的过程中对窗口作出一次性初始化! // 该函数播放一段音频文件 // 第一个参数指定一段音频文件,有多种方式指定,如文件名形式或资源文件的形式 // 只有当第一个参数为资源文件时第二个参数才有用 // 第三个参数表示播放的形式,SND_FILENAME表示以文件名的形式指定音频(即第一个参数是文件名) // SND_ASYNC表示以异步方式播放,即声音一播放PlaySound函数就返回而无需等待 PlaySound( TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC ); return 0; case WM_PAINT: // 重刷消息的处理,窗口形状大小的改变以及被遮盖部分重新显露等都会触发重刷消息 // !关于重绘消息触发的基本原理会在后面介绍 hDC = BeginPaint( hWnd, &ps ); // 这一步为重绘客户区做准备,同时将GDI驱动设备和具体的窗口相关联 // 这就表示GDI将在hWnd所指定窗口的客户区进行绘图以及信息文本输出 // 首先会将客户区中的无效区域用WNDCLASS中指定的画刷重刷(即重刷背景) // 接下来到EndPaint之前才是重现客户区的具体图案和信息文字等 GetClientRect( hWnd, &rect ); // 获得当前客户区无效矩形区域的信息,将信息保存在rect结构体中 DrawText( hDC, TEXT("Hello Windows!"), -1, &rect, // 绘制文本信息!之后会详细讲解 DT_SINGLELINE | DT_CENTER | DT_VCENTER ); EndPaint( hWnd, &ps ); // 结束重绘,将GDI资源释放 return 0;// !响应WM_PAINT函数必须使用BeginPaint和EndPaint函数! case WM_CLOSE: // 关闭窗口的消息(右上角的关闭按钮被触发) if ( IDYES == MessageBox( hWnd, TEXT("是否要退出程序?"), TEXT("提示"), MB_YESNO ) ) // 如果用户点击的是Yes按钮(即表示确认退出程序) { DestroyWindow( hWnd ); // 先销毁窗口的画面,如果成功则返回一个非0值,否则返回0 // 同时自动产生一个WM_DESTROY消息绕过消息队列直接传给过程函数 } return 0; case WM_DESTROY: // 窗口销毁消息的处理 PostQuitMessage( 0 ); // 产生一个WM_QUIT消息投递到消息队列中去 // 可以终止while循环让其停止并推出WinMain // 参数0作为WM_QUIT消息的附加参数wParam处理 // 一般该参数直接作为WinMain函数的退出码 return 0; } // 对于不感兴趣的消息必须使用该函数进行默认处理,否则会出现异常(无法进行其它正常行为) return DefWindowProc( hWnd, uMsg, wParam, lParam ); // 参数即为WindowProc的四个参数 }
6. 函数所属头文件列举:
<winuser.h>:
LoadIcon
LoadCursor
RegisterClass
MessageBox
CreateWindow
ShowWindow
UpdateWindow
GetMessage
TranslateMessage
DispatchMessage
BeginPaint
EndPaint
GetClientRect
DrawText // 虽然看上去应该是个GDI函数,但是已经是非常高层(应用层级),因此位于winuser.h中
DestroyWindow
PostQuitMessage
DefWindowProc
<wingdi.h>:
GetStockObject
<mmsystem.h>:mm=Multi-Media,即多媒体的意思
PlaySound
7. 注意事项:
由于MessageBox中用到了中文,因此需要设定UNICODE宏
由于用到了多媒体工具PlaySound因此需要添加一个静态库winmm.lib
Project -> settngs(或者直接Alt + F7):
UNICODE宏设置:C/C++ -> Category -> Preprocessor -> Preprocessor Definition中添加UNICODE宏
winmm.lib库添加:Link -> Category -> General -> Object/Library Modules中添加winmm.lib