Windows程序绘制窗口的过程
2.2.2 第9练:注册窗口类
/*------------------------------------
009 编程达人win32 API每日一练
第9个例子RegisterClass.c:注册窗口类
WNDCLASS结构
RegisterClass 函数
(c) www.bcdaren.com 编程达人
----------------------------------------*/
#include <windows.h>
/**************回调函数**********
LRESULT:返回值,被宏定义为long型
CALLBACK:函数参数进栈顺序(从右到左,反汇编求证)---_stdcall调用约定
4个参数:为MSG结构体的前4个成员变量
**********************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/***********程序入口 **********/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{ // 窗口类名---一个以\0结尾字符串数组,用来保存程序名称,
//即注册到操作系统的名称,一般为进程名称!
static TCHAR szAppName[] = TEXT("HelloWin");
//*************1、设计窗口类************
WNDCLASS wndclass; //定义窗口变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口类型:水平、垂直重绘,即改变窗口大小时自动发送WM_PAINT消息,而不必手动发送。被改变时该类所有窗口重绘
wndclass.lpfnWndProc = WndProc; //窗口处理函数---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景白色
wndclass.lpszMenuName = NULL; //装载窗口菜单,此处程序没有
wndclass.lpszClassName = szAppName; // 窗口类名---程序的名字
//****************2、注册窗口类**************
if (RegisterClass(&wndclass)) //成功注册一个窗口类
{
MessageBox(NULL, TEXT("爱达人---成功注册一个窗口类!"),
szAppName, MB_ICONEXCLAMATION);
}
return 0;
}
//************回调函数——消息处理过程***********
//窗口回调函数,此函数只有声明和定义,没有调用!这说明此函数确实是由操作系统调用的!
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return 0; //未处理任何窗口消息
}
/*
窗口总是基于窗口类创建的。窗口类确定了处理窗口消息的窗口过程。
在创建应用窗口之前,必须调用函数RegisterClass来注册窗口类。
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
本例中未作任何消息处理,后面的例题中将会详细介绍
*******************************************************************
WNDCLASS结构:
typedef struct
{
UINT style ;
WNDPROC lpfnWndProc ;
int cbClsExtra ;
int cbWndExtra ;
HINSTANCE hInstance ;
HICON hIcon ;
HCURSOR hCursor ;
HBRUSH hbrBackground ;
LPCTSTR lpszMenuName ; //LPCWSTR (Unicode版本)
LPCTSTR lpszClassName ; //LPCWSTR (Unicode版本)
}
WNDCLASS, * PWNDCLASS ;
*********************************
RegisterClass 函数:注册一个窗口类,供以后在对CreateWindow或CreateWindowEx函数的调用中使用。
【注意】如果使用reateWindowEx函数创建创建,相应的需要使用RegisterClassEx注册窗口。但是,如果不需要设置类小图标,则仍然可以使用CreateWindow函数和RegisterClass函数。
ATOM RegisterClassA(
const WNDCLASSA *lpWndClass //指向WNDCLASS结构的指针。在将结构传递给函数之前,必须用适当的类属性填充该结构。
);
返回值编辑
ATOM类型为Windows中定义的新数据类型,其即unsigned short类型,在<WinDef.h>中的定义如下:
typedef WORD ATOM;
typedef unsigned short WORD;
如果函数成功,返回值是唯一标识已注册的类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。
*/
运行结果:注册成功后,返回值非0,弹出一个对话框。
图2-3 注册窗口类
毫无疑问,仅仅只是注册窗口类是无法完成窗口队的创建。
【注意】程序中的窗口过程WndProc并未做任何事情,直接返回操作系统。让我们继续进行下一步。
2.2.3 第10练:创建、显示和更新窗口
/*-----------------------------------
010 编程达人win32 API每日一练
第10个例子CreateWindow.c:创建、显示和更新窗口---此例尚不能显示窗口
CreateWindow函数
ShowWindow函数
UpdateWindow函数
(c) www.bcdaren.com 编程达人
-----------------------------------------*/
#include <windows.h>
/**************回调函数***********/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/***********程序入口 ************/
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin"); // 窗口类名
//************1、设计窗口类*************
WNDCLASS wndclass; //定义窗口类结构变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口风格
wndclass.lpfnWndProc = WndProc; //窗口过程---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //初始化窗体背景(白色画刷)
wndclass.lpszMenuName = NULL; //装载窗口菜单(此处程序没有菜单)
wndclass.lpszClassName = szAppName; // 窗口类名---程序名
/***************2、注册窗口类*********/
if (!RegisterClass(&wndclass)) //返回值为0,未成功注册
{
MessageBox(NULL, TEXT("注册失败!"),
szAppName, MB_ICONEXCLAMATION);
return 0;
}
/*************3、创建窗口******************/
HWND hwnd;
hwnd = CreateWindow(szAppName, //已注册的窗口类名
TEXT("我的第一个窗口程序!"),//窗体标题
WS_OVERLAPPEDWINDOW, //窗体风格(重叠窗口、标题栏和边框
CW_USEDEFAULT, //窗体左上角x坐标
CW_USEDEFAULT, //窗体左上角y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //父窗口句柄,此处NULL
NULL, //窗口菜单句柄,此处NULL
hInstance,//应用程序句柄
NULL //指向一个值的指针,该值传递给窗口WM_CREATE消息
);
/*创建窗口完成(只存在于内存中),系统会发送第一条消息WM_CREATE,注意此时,程序并未执行到消息循环处,操作系统绕过应用程序的消息队列,直接将WM_CREATE消息发送给窗口过程。该消息在所有的消息之前,目的是为了在程序程序执行之前可以有机会进行一些初始化工作或装载动态链接库等操作。
A) CreateWindow 的第一个参数就是窗口类名,通过这个名字可以找到刚才注册的窗口类,然后再根据它来创建窗口。
B) 显示器上的坐标与数学中的不同,显示器的左上角是坐标原点,从原点向右是x轴,向下是y轴,都是正坐标,没有负数。
C) 参数 hInstance 是通过主函数 WinMain 传入的。
【注意】通过 CreateWindows() 函数创建窗口后,仅仅是为窗口分配了内存空间,获得了句柄,但窗口并没有显示出来,所以还需要调用 ShowWindow() 函数来显示窗口。
*/
/*******************4、显示窗口和更新窗口**************/
ShowWindow(hwnd, nCmdShow); //发送WM_SIZE和WM_SHOWWINDOW消息送入消息队列
/*1、单纯一个ShowWindow,照样会正确绘制出窗口内容,只不过是在消息队列取空之后才绘制的,有时我们希望窗口被立即重画,而不是去等待那个不确定的消息队列,此时就需要用到UpdateWindow。UpdateWindow的作用只有一个:假若当前存在被标记为重绘的区域(即无效区域,如果其不存在的话它什么也不做),那么立刻让Windows使用SendMessage的方式来对你的窗口发送WM_PAINT。
2、ShowWindow本身并不发送重绘消息,它的作用仅仅是把窗口显示出来。
所以还需要调用 UpdateWindow() 函数,生成 VM_PAINT 消息,将客户区中的各种控件绘制出来。不过,当窗口显示的时候,Windows会自动探测窗口的内容是否需要重绘、以及需要重绘的区域。
3、ShowWindow调用中会发送WM_SIZE和WM_SHOWWINDOW消息给窗口过程。
4、第一次调用和以后的调用是有区别的。第一次调用时,它的输入参数 nCmdShow 是需要输入WinMain 函数里传入来的nCmdShow 参数,而不能是其它参数。在此以后就不再需要nCmdShow 参数了。
*/
UpdateWindow(hwnd); //发送WM_PAINT消息给窗口过程
/*执行UpdateWindow时会立即发送一条WM_PAINT消息,同样由操作系统直接发出。UpdateWindow执行时,先判断无效区域是否为空,不为空,则发送WM_PAINT消息,为空为不发送。可见,将上面的ShowWindow注释掉,则不发送。因为未显示出来,当然没有无效区域,即无效区域为空。
*/
/*UpdateWindow和In?v?a?l?i?d?a?t?e?的区别:
1、UpdateWindow()的作用是使窗口立即重绘(因为操作系统绕过应用程序的消息队列,直接给窗口过程发WM_PAINT消息,从而导致窗口立即重绘。
而Invalidate等函数(功能为使整个客户区无效)调用后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。
2、UpdateWindow执行时,先判断无效区域是否为空,不为空才发送消息,Invalidate的无效区为整个客户区(注意与InvalidRect函数(功能为使指定的区域无效)的区别)
*/
return 0;
}
//**********回调函数——消息处理过程***********
//窗口过程回调函数,这里只有声明和定义,直接返回!这说明此函数确实是由操作系统调用的!
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return 0; //未处理任何窗口消息
}
/**************************
CreateWindow函数:创建一个重叠窗口,弹出窗口或子窗口。它指定窗口类,窗口标题,窗口样式,以及(可选)窗口的初始位置和大小。
该函数还指定窗口的父级或所有者(如果有)以及窗口的菜单。
要使用CreateWindow支持的样式以及扩展窗口样式,请使用CreateWindowEx函数。
void CreateWindowA(
lpClassName,//窗口的类名
lpWindowName,//窗体标题名称
dwStyle, //指定创建窗口的风格。
x, //窗体左上角x坐标
y, //窗体左上角y坐标
nWidth, //窗口的宽度
nHeight, //窗口的高度
hWndParent, //父窗口句柄
hMenu, //窗口菜单句柄
hInstance, //应用程序句柄
lpParam //指向一个值的指针,该值传递给窗口WM_CREATE消息。
);
*******************************
ShowWindow函数 :该函数设置指定窗口的显示状态。
BOOL ShowWindow(
HWND hWnd, //窗口的句柄。
int nCmdShow //窗口显示的状态
);
返回值:如果窗口之前可见,则返回值为非零。如果窗口之前被隐藏,则返回值为零。
********************************
UpdateWindow函数:如果窗口的更新区域不为空,则UpdateWindow函数通过向该窗口过程发送WM_PAINT消息来更新指定窗口的客户区。
该函数绕过应用程序消息队列,直接将WM_PAINT消息发送到指定窗口的窗口过程。如果更新区域为空,则不发送消息。
UpdateWindow(
HWND hWnd //要更新的窗口句柄
);
*/
注意
1.细心的读者可能会注意到,我们在初始化窗口类时已经给出了窗口风格,为何在调用CreateWindow函数创建窗口时还要再次给出窗口风格呢?
这是因为,一个窗口类可以创建多个窗口,每个窗口都可以有属于自己的独特风格,这样设计更灵活。
2.还有一个重复项,初始化窗口类时给定了窗口菜单,CreateWindow函数同样也有一个参数是菜单句柄。道理是相同的,如果该窗口类只创建了一个窗口,绑定菜单可以在初始化窗口类时进行,也可以在调用CreateWindow时进行。假设一个窗口类需要创建多个窗口时,则每个窗口都可以绑定不同的菜单。
3.虽然此例程已经创建窗口、显示窗口和更新窗口,但新创建的窗口仅存在于内存中,并未真正显示出来。执行CreateWindow函数后,会产生程序的第一个消息WM_CREATE。只有当Windows系统将WM_CREATE消息直接发送给窗口过程WndProc并处理之后,才会真正在屏幕显示窗口。
4.【注意】注释中解释了显示窗口的四个不同函数的区别:ShowWindow、UpdateWindow、Invalidate、InvalidRect。其中执行ShowWindow函数会产生两个消息WM_SIZE和WM_SHOWWINDOW并送入窗口消息队列。执行UpdateWindow函数会产生WM_PAINT消息无需送入窗口消息队列,而是直接交给窗口过程,立即更新窗口。
【注】此时,由于窗口过程尚未处理任何消息,屏幕仍无法显示窗口。
2.2.4 第11练:消息循环
/*------------------------------------
011 编程达人win32 API每日一练
第11个例子GetMessage.c:消息循环
MSG结构
GetMessage函数
TranslateMessage函数:将虚拟键消息转换为字符消息
DispatchMessage函数: 分发一个消息给窗口程序
(c) www.bcdaren.com 编程达人
---------------------------------------*/
#include <windows.h>
/***********回调函数*********************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/***********程序入口 ***************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin"); // 窗口类名
/***********1、设计窗口类*******************/
WNDCLASS wndclass; //定义窗口类变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口风格
wndclass.lpfnWndProc = WndProc; //窗口处理函数---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
wndclass.lpszMenuName = NULL; //装载窗口菜单,此处程序没有
wndclass.lpszClassName = szAppName; // 窗口类名---程序名
/**********2、注册窗口类**************/
if (!RegisterClass(&wndclass)) //未成功注册
{
MessageBox(NULL, TEXT("注册失败!"),szAppName, MB_ICONEXCLAMATION);
return 0;
}
/**************3、创建窗口类************/
HWND hwnd; //定义窗口句柄
hwnd = CreateWindow(szAppName, //已注册窗口类名
TEXT("我的第一个窗口程序!"),//窗体标题
WS_OVERLAPPEDWINDOW, //窗体风格
CW_USEDEFAULT, //窗体左上角x坐标
CW_USEDEFAULT, //窗体左上角y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //父窗口句柄,此处NULL
NULL, //窗口菜单句柄,此处NULL
hInstance,//应用程序句柄
NULL
);
/**************4、显示窗口和更新窗口*************/
ShowWindow(hwnd, nCmdShow); //发送WM_SIZE和WM_SHOWWINDOW消息送入消息队列
UpdateWindow(hwnd); //发送WM_PAINT消息给窗口过程
/**************5、消息循环***************/
MSG msg;//定义消息变量
/*1、调用GetMessage函数时,传入msg的地址,从窗口消息队列中获取消息,其中的消息结构的内容由操作系统自动填充。
2、这是windows程序的核心,消息循环处理过程,只有在收到WM_QUIT时才退出消息循环,结束程序。
*/
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将虚拟键消息转换为字符消息
DispatchMessage(&msg); //发送消息函数,先把msg发送给操作系统,
//然后由操作系统再调用Wndproc函数!
//Dispatch函数的内部实现大体流程
//push 消息参数进栈
//............
//call WndProc(...) //调用窗体回调函
//数,注意这里是个回调函数。
//................
//结束,返回
}
return msg.wParam;//msg.wParam 来自一条表示退出的消息,返回这个值给系统
}
/**********回调函数——消息处理过程***********/
//窗口回调函数,只有声明和定义,直接返回!这说明此函数确实是由操作系统调用的!
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return 0; //未处理任何窗口消息
}
/*********************
MSG结构:
typedef struct tagMSG {
HWND hwnd; //消息所指向的窗口的句柄
UINT message; //每条消息在头文件WINUSER.H中都定义了一个以WM为前缀的消息标识符,如按下鼠标左键WM_lbuttondown。
WPARAM wParam; //一个32为的“消息参数”,该参数的含义和取值取决于具体的消息
LPARAM lParam; //一个32位的消息参数,该参数的含义和取值同样取决于具体的消息
DWORD time; //消息进入消息队列的时间
POINT pt; //消息进入消息队列中时鼠标指针的位置坐标
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
*****************************************
GetMessage函数:从调用线程的消息队列里取得一个消息并将其放于指定的结构。
BOOL WINAPI GetMessageA(
_Out_ LPMSG lpMsg, //指向MSG结构的指针,该结构从线程的消息队列接收消息信息
_In_opt_ HWND hWnd,//想获取哪个窗口的消息,为NULL 时是获取所有窗口的消息
_In_ UINT wMsgFilterMin,//获取消息的 ID 编号最小值,如果小于这个值就不获取
_In_ UINT wMsgFilterMax);//获取消息的 ID 编号最大值,如果大于这个值就不获取
如果wMsgFilterMin和wMsgFilterMax都为零,则GetMessage返回所有可用消息(即,不执行范围过滤)。
__in:输入参数;__out:输出参数;__in_opt:可选的输入参数,返回后不会改变其值。
**************************************
TranslateMessage函数:将虚拟键消息转换为字符消息。字符消息将发布到调用线程的消息队列中,以在线程下次调用GetMessage或PeekMessage函数时读取。
BOOL TranslateMessage(
const MSG *lpMsg //指向MSG结构的指针,该结构包含通过使用GetMessage或
//PeekMessage函数从调用线程的消息队列中检索到的消息信息。
);
*************************************
DispatchMessage函数:将消息调度到窗口过程。它通常用于调度由GetMessage函数检索的消息。
LRESULT DispatchMessage(
const MSG *lpMsg //指向包含消息的结构的指针。
);
*/
提示
消息循环是Windows程序的核心,主程序中使用一个while循环结构不间断的从窗口消息队列中获取消息,GetMessage函数获取到WM_QUIT标记后返回0,退出消息循环。如果获取的是其它消息,则交给TranslateMessage函数和DispatchMessage函数处理。如果从窗口消息队列中获取的是键盘按键消息WM_KEYDOWN,则通过TranslateMessage函数将键盘消息的虚拟键码翻译字符并转换成WM_CHAR消息后分发给操作系统,再次送入消息队列。如果获取的是非WM_KEYDOWN消息,则直接交给DispatchMessage函数分发给Windows操作系统,Windows操作系统再回调窗口过程处理消息。
【注】此时,由于窗口过程尚未处理任何消息,屏幕仍无法显示窗口。
2.2.5 第12练:窗口过程
/*---------------------------------------
012 编程达人win32 API每日一练
第12个例子WndProc.c:处理消息机制 ---窗口过程
WndProc 函数
WM_CREATE消息
DefWindowProc函数
注意:关闭窗口后,程序仍然没有退出。
(c) www.bcdaren.com 编程达人
---------------------------------------*/
#include <windows.h>
/*************回调函数************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/*************程序入口 *****************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin"); // 窗口类名
/*****************1、设计窗口类*********/
WNDCLASS wndclass; //定义窗口类变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口风格
wndclass.lpfnWndProc = WndProc; //窗口处理函数---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
wndclass.lpszMenuName = NULL; //装载窗口菜单,此处程序没有
wndclass.lpszClassName = szAppName; // 窗口类名---程序的名字
/**************2、注册窗口类********/
if (!RegisterClass(&wndclass)) //未成功注册
{
MessageBox(NULL, TEXT("注册失败!"),szAppName, MB_ICONEXCLAMATION);
return 0;
}
/*************3、创建窗口***************/
HWND hwnd;
hwnd = CreateWindow(szAppName, //已注册的窗口类名
TEXT("我的第一个窗口程序!"),//窗体标题
WS_OVERLAPPEDWINDOW, //窗体风格
CW_USEDEFAULT, //窗体左上角x坐标
CW_USEDEFAULT, //窗体左上角y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //父窗口句柄,此处NULL
NULL, //窗口菜单句柄,此处NULL
hInstance,//应用程序句柄
NULL
);
/*创建窗口完成(只存在于内存),系统会发送第一条消息WM_CREATE,注意此时,程序并未执行到消息循环处,操作系统绕过应用程序的消息队列,直接向应用程序发出。该消息在所有的消息之前,目的是为了在程序程序执行之前可以有机会进行一些初始化工作或装载动态链接库等操作。
A) CreateWindow 的第一个参数就是窗口类名,通过这个名字可以找到刚才注册的窗口类,然后再根据它来创建窗口。
B) 显示器上的坐标与数学中的不同,显示器的左上角是坐标原点,从原点向右是x轴,向下是y轴,都是正坐标,没有负数。
C) 参数 hInstance 是通过主函数 WinMain 传入的。
【注意】通过 CreateWindows() 函数创建窗口后,仅仅是为窗口分配了内存空间,获得了句柄,但窗口并没有显示出来,所以还需要调用 ShowWindow() 函数来显示窗口。
*/
/****************4、显示窗口和更新窗口************/
ShowWindow(hwnd, nCmdShow); //发送WM_SIZE和WM_SHOWWINDOW消息送入消息队列
UpdateWindow(hwnd); //发送WM_PAINT消息给窗口过程
/****************5、消息循环***************/
MSG msg; //定义消息变量
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将虚拟键消息转换为字符消息
DispatchMessage(&msg); //分发消息函数
}
return msg.wParam;//msg.wParam 来自一条表示退出的消息,返回这个值给系统,从而退出
}
/************************回调函数——消息处理过程****************************/
//窗口回调函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{//创建窗口消息,此消息是一个应用程序发送的第一个消息,也是唯一的一次!
case WM_CREATE:
return 0;
//break; //也可以
default: //可以放在最后
//调用默认窗口过程以为应用程序未处理的任何窗口消息提供默认处理
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
//return DefWindowProc(hwnd, message, wParam, lParam); //默认窗口过程
}
/***************注意******************
WndProc 函数:
1:所有的消息都由操作系统负责管理,所有的消息先进入总的系统消息队列,再由系统消息队列分发给各个窗口消息队列!
2:窗口回调函数Wndproc是由操作系统调用的,所有的队列消息进入消息循环,由消息循环把消息分发给操作系统,再由操作系统调用窗口过程Wndproc函数,而Wndproc窗口过程根据message的不同而调用相应的处理过程!如果是非队列消息则由操作系统直接交给窗口过程。窗口过程即回调函数,在switch语句中使用message参数,其中各个消息由单独的case语句处理。
请注意,每种情况都会为每个消息返回一个特定的值。对于未处理的消息,窗口过程将调用操作系统默认的窗口过程DefWindowProc函数处理。
3:上面这个程序可以改造一下,再添加几个消息处理过程,比如鼠标左键按下WM_LBUTTONDOWN,鼠标左键抬起WM_LBUTTONUP定时器消息WM_TIMER,绘图消息WM_PAINT消息等等。我们将注意测试每个消息!
4:其实,WINDOWS程序也可以自己去创建函数!自己去自定义消息处理过程!比如,自己定义了一个函数,想在鼠标左键点击后去调用这个函数,那就可以在WM_LBUTTONDOWN这个消息处理过程中加上自己定义的函数调用就可以了!
5:上面这个程序是个非常简单的程序,但是他足以说明WINDOWS程序的消息机制!几乎所有的程序都包括上面这段代码。不管多么复杂的WINDOWS程序都必须包括上面这段代码,因为他是再简单不过的了,只是创建了一个窗口!
但是,再复杂的程序无非也就是在Wndproc函数中的switch多加几个case嘛!无非就是加上什么鼠标,键盘,定时器等等,但原里都是一样的!他的消息处理过程都是一样的!主要是把他的消息机制弄懂,其它的都非常easy!
*******************************/
/*
WM_CREATE消息:当应用程序通过调用CreateWindowEx或CreateWindow函数请求创建窗口时发送。(在函数返回之前发送消息。)
在创建窗口之后,但在该窗口变为可见之前,新窗口的窗口过程会收到此消息。窗口通过其WindowProc函数接收此消息。
#define WM_CREATE 0x0001
指向CREATESTRUCT结构的指针,该结构包含有关正在创建的窗口的信息。
返回值类型:LRESULT
如果应用程序处理此消息,则应返回零以继续创建窗口。如果应用程序返回–1,则窗口将被销毁,并且CreateWindowEx或CreateWindow函数将返回NULL句柄。
*******************************************
DefWindowProc函数:调用Windows操作系统默认的窗口过程来为应用程序没有处理的任何窗口消息提供转换的处理。该函数确保每一个消息得到处理。
绝大多少消息都是由DefWindowProc函数按照默认的方式处理。
LRESULT LRESULT DefWindowProcA(
HWND hWnd,//接收到消息的窗口过程的句柄。
UINT Msg,//消息。
WPARAM wParam,//附加消息信息。此参数的内容取决于Msg参数的值。
LPARAM lParam//附加消息信息。此参数的内容取决于Msg参数的值。
);
返回值类型:LRESULT。返回值是消息处理的结果,并取决于消息。
*/
运行结果:
提示
1.窗口过程是一个回调函数,Windows操作系统将消息参数传递给WndProc窗口过程处理消息。窗口过程内是一个switch结构,根据message消息ID使用case语句处理。本例case WM_CREATE:语句块中什么都没有处理,直接return 0返回操作系统,我们创建的窗口就在屏幕上显示出来了。
2.WM_CREATE消息是Windows程序产生的第一个消息,也是唯一一次产生的消息。通常我们在处理WM_CREATE消息时做一些窗口的初始化工作,例如添加子窗口。
3.细心的读者会发现,当我们点击窗口右上角系统菜单关闭窗口后,程序仍然没有退出,这是怎么回事呢?是否还记得,我们讲解消息循环时,只有当GetMessage函数获取WM_QUIT标记时才会结束消息循环,退出程序。因此,虽然我们关闭了窗口,但是消息循环仍然在运行,等待获取新的窗口消息。
4.动手实验:为了加深映像,理解Windows程序的消息驱动机制,请读者在case WM_CREATE:处下一个断点,然后单步跟踪程序的运行,观察程序执行的流程。我们会惊讶的发现,Windows程序并不是按照代码的先后顺序执行的,这与我们之前学习的面向过程的C语言或汇编语言程序完全不同。当我们处理WM_CREATE消息return 0返回后,返回到了窗口过程的结尾,然后跳转到CreateWindow函数,接着执行ShowWindow函数后,屏幕出现窗口。这也证明了Windows程序是跟着消息传递的顺序执行的。我们看到的Windows程序仅仅是浮出水面的冰山一角。在后面的学习中,希望读者能够多做一些这样的实验,体会消息驱动机制。
5.【注意】switch结构的语法,DefWindowProc函数可以放在default语句后,也可以直接放到return语句后。如果将DefWindowProc函数放置在return语句后,则case语句中可以直接使用return 0;返回。