第一次接触计算机的时候,已经是2005年,我记得当时在学校的机房还有98和2000的操作系统, 当时学C语言后,知道了怎么在cmd一样的模式下编制程序,当时一直迷惑就是怎么样编制一个和IE和Word一样具有点击鼠标操作的应用程序, 后来过了大一,大二时选择了自动化(我们学习大一不分专业)就一直没有机会学习如何编制Windows下的应用程序。
出于兴趣,现在重新开始学习Windows下的图形界面应用程序编写。
看了孙老师的视频教程,讲的很好,很可惜我基础太差,听不懂, 于是就从网上下载了那本经典的教程开始自学Windows程序设计。最难的是入门,入门后再加上自己的学习,编制一个简单的程序基本是不成问题的了。
下面开始这次的闲扯, Windows消息机制:
早DOS下编程,程序的执行过程是按照程序任意的意愿来执行的, 那时应用程序干什么,以及什么时候干什么那时由程序员说了算的, 在windows下情况就不那么妙了, windows程序是面向使用者的, windows应用程序的执行过程基本上是由使用人员的操作过程来决定的。
windows会为应用程序建立和维护一个消息队列,这个消息队列会存储预定义的消息(windows预定义了一大堆使用者使用过程中产生的消息,反正很多,没有数,估计上万吧);当操作一个应用程序的时候,windows操作系统感知使用者的操作并将这个消息放到应用程序的消息队列,应用程序根据消息进行相应的处理。这就是大体的windows消息机制,这个是我的简单理解,如果需要理解可以参考那本经典的教材.......
一、windows程序的过程
windows程序的过程是:
程序入口
定义窗口类
注册窗口类
生成窗口
更新窗口
显示窗口
消息循环
窗口消息处理(回调函数, 我不知道callback函数是不是回调函数, 不过从call back上看估计就是......哈哈哈 )
程序执行实体结束
1、windows程序的入口:
与Dos下的程序入口不一样,windows下的程序入口如下:
int WINAPI WinMain(HINSTANCE hInstance, //本程序的实例句柄 HINSTANCE hPrevInstance, //程序的前一个实例的句柄 LPSTR lpCmdLine, int iCmdShow)
其中WINAPI是一个预定义的宏:
#define WINAPI __stdcall
这个宏定义指明函数的参数的入栈和出栈顺序。
2、 定义窗口类型:
windows程序主要是以窗口为对象的, 应用程序的一个按钮可以称作窗口,一个滚动条和状态条同样是窗口,而整个程序显示出来的也是窗口。在windows为窗口定义了一个结构体:
typedef struct tagWNDCLASSA
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASSA;
typedef WNDCLASSA WNDCLASS;
为了要使用窗口需要定义窗口变量(结构体变量);如:
Exp:
WNDCLASS wndclass;
定义完后需要进行初始化,如下:
wndclass.cbClsExtra =0; //声明程序额外空间用变量
wndclass.cbWndExtra=0; //额外变量
wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //GetStockObject()获取一个对象,这里为获取一个画刷对象
//hbrBackGround字段名称中的hbr前缀代表画刷句柄,画刷是个绘图词汇,指
//用来填充一个区域的着色样式,Windows有几个标准的画刷,也称为备用Stock画刷
//这里的GetStockObject()函数呼叫返回一个白色画刷句柄,这就表示窗口的显示区域
//背景完全为白色
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); //加载光标
//LoadCursor()函数第一个参数是句柄,第二个参数是要加载的光标类型
wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //加载程序图标
//LoadIcon()函数第一个参数是句柄,第二个参数是要加载的图标类型,这里加载预定义图标
//对于预定义的图标,第二个参数是IDI开头的标识符,在Winuser.h中定义
//当要加载用户自定义图标时,第一个参数必须设定为程序执行实体的句柄hInstance
wndclass.hInstance=hInstance; //系统分配给应用程序的实例句柄
wndclass.lpfnWndProc=WndProc; //a pointer which point to the main window's callback function函数的指针
wndclass.lpszClassName=szAppName; //窗口名称,创建窗口的时候需要用到
wndclass.lpszMenuName=NULL; //菜单资源名称
wndclass.style =CS_HREDRAW | CS_VREDRAW; //窗口的类型,或者风格
3、注册窗口类
为了可以显示一个窗口,必须先向windows注册一个窗口类, 以此来通知windows,应用程序需要用这样一个窗口类来显示。在windows中通过 RegisterClass函数来注册窗口类。如下:
Exp:
//注册窗口类型
if(!RegisterClass(&wndclass))
//int registerclass(WNDCALSS *wndclass)
//函数注册窗口类型,这个函数有两个类型定义
//RegisterClassA: 返回一个指向WNDCLASSA结构的指针
//RegisterClassW: 返回一个指向WNDCLASSW结构的指针
//如果用定义的UNICODE标识符编译了程序,程序将呼叫RegisterClassW,改程序可以在NT上执行,但是在98上就不能真正执行,
//函数有一个进入点,但是会执行错误,返回一个0值,这就是为什么要进行判断的原因,
//为了防止错误,就需要判断后退出程序。
{
MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR);
return 0;
}
4、生成窗口
一个窗口里面包含很多的内容,在计算机里面需要很多的内存空间来保存这些内容。同时在注册窗口类型后,并没有生成一个窗口实体,就像有了一个模具,但是还没有用模具制作工件一样。为了得到工件我们需要一个制作工件的过程。在这里就是需要生成工件,如下:
//生成窗口
hwnd=CreateWindow(szAppName, //窗口类名, 在匈牙利命名规则下以sz字符开头的标识符表示的是以'\0'结束的字符串
TEXT("The Hello Program"), //窗口的标题栏的提示
WS_OVERLAPPEDWINDOW, //窗口样式,标注样式窗口,具有标题栏,并且标题栏有缩小、放大和关闭按钮。
//WS_OVERLAPPEDWINDOW,其实几个位旗标的组合值
// #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | \
// WS_CAPTION | \
// WS_SYSMENU | \
// WS_THICKFRAME | \
// WS_MINIMIZEBOX | \
// WS_MAXIMIZEBOX )
CW_USEDEFAULT, //初始化窗口的X轴起始位置,
CW_USEDEFAULT, //初始化窗口的Y轴起始位置
CW_USEDEFAULT, // initial x size 初始的X方向的大小
CW_USEDEFAULT, // initial y size 初始的Y方向的大小
NULL, //父窗口的句柄,通常子窗口显示在父窗口的前面
//应用程序的窗口显示在桌面的上面,应用程序不必为呼叫CreateWindow函数获取桌面的句柄
NULL, //窗口的菜单句柄
hInstance, //程序的执行实例句柄
NULL //附加参数信息
);
//在调用窗口产生函数createwindow函数后,系统会为窗口分配内存,用来保存createwindow函数制定的窗口信息和其他信息,
//createwindow返回的句柄值是程序后面获取存储在内存信息的指针值。
5、显示和更新窗口
在制作完工件后,我们需要将它拿出来才会有用;在windows下,生成窗体后,他并不会立即显示,而是在内存中占用了一段空间,我们需要利用一个工具将它显示在桌面: showwindow和updatewindow。如下:
ShowWindow(hwnd,iCmdShow); //显式窗口,两个参数,第一个参数是CreateWindow函数返回的窗口句柄
//第二个参数,确定窗口如何显示在屏幕上,例如:最大化、最小化还是一般化
//通常iCmdShow: SW_SHOWNORMAL
// SW_SHOWMAXIMIZED
// SW_SHOWMINNOACTIVE 窗口仅显示在工作列上
//如果icmdshow=sw_shownormal,则窗口的显示区域会被窗口类别中定义的背景画刷所覆盖。
UpdateWindow(hwnd); //更新窗口, 利用updatewindow函数会重画显示区域,这里是通过给窗口处理函数发送一个WM_PAINT消息完成这一过程
6、消息循环
窗口显示到桌面上后,程序的使用者对窗口进行操作,会产生一大堆的消息, 应用程序通过消息循环从消息队列取出消息,然后进行处理,就形成了应用程序的处理过程。
消息循环通过下面的样式进行处理:
Exp:
//消息循环
//windows为每一个应用程序维护一个消息队列,在发生输入事件后,windows将事件转化为一个消息并将消息放到消息队列中
//应用程序通过消息循环取系统发给应用程序的消息,并对之做出响应
//消息结构体MSG
//
// typedef struct tagMSG
// {
// HWND hwnd; 接受消息的窗口的句柄
// UINT message; 消息标识符,表示具体的消息信息
// WPARAM wParam; 32位的附加消息参数。根据消息的不同而不同
// LPARAM lParam; 32位的附加消息参数,其值与消息有关
// DWORD time; 消息放入消息队列的时间
// POINT pt; 消息放入消息队列时鼠标的坐标
// }
// MSG, *PMSG;
//其中POINT也是一个结构体
// typedef struct tagPOINT
// {
// LONG x;
// LONG y;
// }
// POINT, *PPOINT;
while(GetMessage(&msg,NULL,0,0))
//getmessage函数取得windows系统发送给程序的消息,每次从消息队列里面取一个值
//第二、第三、第四个参数设定为NULL,0,0 表示程序接收自己建立的所有窗口的所有消息
//当从消息队列取回的MSG结构体的message子段位WM_QUIT消息是,GetMessage就返回一个 0 值
//否则就返回非零值
//在匈牙利命名法中:
// WM_ 开头的标识符表示的是消息
{
TranslateMessage(&msg); // 将消息结构体MSG回传给Windows,进行一些键盘转化
DispatchMessage(&msg); //将msg结构体回传给Windows。然后,Windows将该消息发送给适当的窗口处理程序,让其进行处理
//即: Windows呼叫回调函数,回调函数处理完消息后,返回到Windows,此时
// Windows还停留在DispatchMessage呼叫中,在结束DispatchMessage呼叫处理之后,Windows回到程序执行
//实体,并进行下一轮的消息循环
}
return msg.wParam;
}
7、窗口消息处理函数
前面说到应用程序可以取得消息,但是我们看到消息循环并不对消息进行处理,因此为了处理消息,我们就需要建立一种机制,这就是窗口消息处理程序。
我们通过下面的方式来进行窗口消息处理:
Exp:
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
//关于窗口处理函数的参数的意义
//第一个参数:HWND hwnd, 表示的是接收消息的窗口的句柄,这个句柄值与CreateWindow函数返回的值相等
//若用同一WNDCLASS建立多个窗口,则hwnd标识特定的某个窗体
//第二个参数:UINT message, 为标识窗体的数值,最后两个参数都是32位的消息的附加信息参数
//程序本身通常自己不呼叫窗口消息处理程序,而由系统呼叫窗口消息处理函数
//通过SendMessage()函数可以实现程序自己呼叫窗口消息处理函数
//Windows程序写作者使用switch和case结构处理消息; 窗口消息处理程序在处理消息时,必须回传一个0.
//窗口消息处理程序不予处理的所有消息应该被传给名为DefWindowProc的Windows函数,而且从DefWindowProc传回的值必须由窗口消息处理程序传回
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
//case WM_CREATE:
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
GetClientRect(hwnd,&rect);
DrawText(hdc,TEXT("Hello win"),-1,&rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd,&ps);
return 0; //消息处理完后必须返回0值到系统
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,message,wParam,lParam); //这个函数的返回值必须由窗口消息处理程序返回到系统
}
我们看不到这个函数的调用,但是他是怎么执行的呢?是这样的: 当使用者进行操作后,windows操作系统感知这个操作,然后将消息投递到消息队列,同时windows会呼叫用户编制的窗口消息处理程序。
Tip:
注意是windows呼叫用户编制的窗口消息处理程序。
通过我自己的写的一个简单的windows程序来看看一个完整的windows应用程序:
// 第一个Win32 Program #include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); //declare a callback function int WINAPI WinMain(HINSTANCE hInstance, //本程序的实例句柄 HINSTANCE hPrevInstance, //程序的前一个实例的句柄 LPSTR lpCmdLine, // int iCmdShow) { static TCHAR szAppName[]=TEXT("Hellowin"); // 这个就是窗口的caption HWND hwnd; //声明句柄变量 MSG msg; //声明消息结构体 定义消息结构体变量 WNDCLASS wndclass; //声明窗口结构体变量 //声明定义窗口结构体变量后初始化结构体 wndclass.cbClsExtra =0; //声明程序额外空间用变量 wndclass.cbWndExtra=0; //额外变量 wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //GetStockObject()获取一个对象,这里为获取一个画刷对象 //hbrBackGround字段名称中的hbr前缀代表画刷句柄,画刷是个绘图词汇,指 //用来填充一个区域的着色样式,Windows有几个标准的画刷,也称为备用Stock画刷 //这里的GetStockObject()函数呼叫返回一个白色画刷句柄,这就表示窗口的显示区域 //背景完全为白色 wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); //加载光标 //LoadCursor()函数第一个参数是句柄,第二个参数是要加载的光标类型 wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //加载程序图标 //LoadIcon()函数第一个参数是句柄,第二个参数是要加载的图标类型,这里加载预定义图标 //对于预定义的图标,第二个参数是IDI开头的标识符,在Winuser.h中定义 //当要加载用户自定义图标时,第一个参数必须设定为程序执行实体的句柄hInstance wndclass.hInstance=hInstance; //系统分配给应用程序的实例句柄 wndclass.lpfnWndProc=WndProc; //a pointer which point to the main window's callback function函数的指针 wndclass.lpszClassName=szAppName; //窗口名称,创建窗口的时候需要用到 wndclass.lpszMenuName=NULL; //菜单资源名称 wndclass.style =CS_HREDRAW | CS_VREDRAW; //窗口的类型,或者风格 //注册窗口类型 if(!RegisterClass(&wndclass)) //int registerclass(WNDCALSS *wndclass) //函数注册窗口类型,这个函数有两个类型定义 //RegisterClassA: 返回一个指向WNDCLASSA结构的指针 //RegisterClassW: 返回一个指向WNDCLASSW结构的指针 //如果用定义的UNICODE标识符编译了程序,程序将呼叫RegisterClassW,改程序可以在NT上执行,但是在98上就不能真正执行, //函数有一个进入点,但是会执行错误,返回一个0值,这就是为什么要进行判断的原因, //为了防止错误,就需要判断后退出程序。 { MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR); return 0; } //生成窗口 hwnd=CreateWindow(szAppName, //窗口类名, 在匈牙利命名规则下以sz字符开头的标识符表示的是以'\0'结束的字符串 TEXT("The Hello Program"), //窗口的标题栏的提示 WS_OVERLAPPEDWINDOW, //窗口样式,标注样式窗口,具有标题栏,并且标题栏有缩小、放大和关闭按钮。 //WS_OVERLAPPEDWINDOW,其实几个位旗标的组合值 // #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | \ // WS_CAPTION | \ // WS_SYSMENU | \ // WS_THICKFRAME | \ // WS_MINIMIZEBOX | \ // WS_MAXIMIZEBOX ) CW_USEDEFAULT, //初始化窗口的X轴起始位置, CW_USEDEFAULT, //初始化窗口的Y轴起始位置 CW_USEDEFAULT, // initial x size 初始的X方向的大小 CW_USEDEFAULT, // initial y size 初始的Y方向的大小 NULL, //父窗口的句柄,通常子窗口显示在父窗口的前面 //应用程序的窗口显示在桌面的上面,应用程序不必为呼叫CreateWindow函数获取桌面的句柄 NULL, //窗口的菜单句柄 hInstance, //程序的执行实例句柄 NULL //附加参数信息 ); //在调用窗口产生函数createwindow函数后,系统会为窗口分配内存,用来保存createwindow函数制定的窗口信息和其他信息, //createwindow返回的句柄值是程序后面获取存储在内存信息的指针值。 ShowWindow(hwnd,iCmdShow); //显式窗口,两个参数,第一个参数是CreateWindow函数返回的窗口句柄 //第二个参数,确定窗口如何显示在屏幕上,例如:最大化、最小化还是一般化 //通常iCmdShow: SW_SHOWNORMAL // SW_SHOWMAXIMIZED // SW_SHOWMINNOACTIVE 窗口仅显示在工作列上 //如果icmdshow=sw_shownormal,则窗口的显示区域会被窗口类别中定义的背景画刷所覆盖。 UpdateWindow(hwnd); //更新窗口, 利用updatewindow函数会重画显示区域,这里是通过给窗口处理函数发送一个WM_PAINT消息完成这一过程 //消息循环 //windows为每一个应用程序维护一个消息队列,在发生输入事件后,windows将事件转化为一个消息并将消息放到消息队列中 //应用程序通过消息循环取系统发给应用程序的消息,并对之做出响应 //消息结构体MSG // // typedef struct tagMSG // { // HWND hwnd; 接受消息的窗口的句柄 // UINT message; 消息标识符,表示具体的消息信息 // WPARAM wParam; 32位的附加消息参数。根据消息的不同而不同 // LPARAM lParam; 32位的附加消息参数,其值与消息有关 // DWORD time; 消息放入消息队列的时间 // POINT pt; 消息放入消息队列时鼠标的坐标 // } // MSG, *PMSG; //其中POINT也是一个结构体 // typedef struct tagPOINT // { // LONG x; // LONG y; // } // POINT, *PPOINT; while(GetMessage(&msg,NULL,0,0)) //getmessage函数取得windows系统发送给程序的消息,每次从消息队列里面取一个值 //第二、第三、第四个参数设定为NULL,0,0 表示程序接收自己建立的所有窗口的所有消息 //当从消息队列取回的MSG结构体的message子段位WM_QUIT消息是,GetMessage就返回一个 0 值 //否则就返回非零值 //在匈牙利命名法中: // WM_ 开头的标识符表示的是消息 { TranslateMessage(&msg); // 将消息结构体MSG回传给Windows,进行一些键盘转化 DispatchMessage(&msg); //将msg结构体回传给Windows。然后,Windows将该消息发送给适当的窗口处理程序,让其进行处理 //即: Windows呼叫回调函数,回调函数处理完消息后,返回到Windows,此时 // Windows还停留在DispatchMessage呼叫中,在结束DispatchMessage呼叫处理之后,Windows回到程序执行 //实体,并进行下一轮的消息循环 } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) //关于窗口处理函数的参数的意义 //第一个参数:HWND hwnd, 表示的是接收消息的窗口的句柄,这个句柄值与CreateWindow函数返回的值相等 //若用同一WNDCLASS建立多个窗口,则hwnd标识特定的某个窗体 //第二个参数:UINT message, 为标识窗体的数值,最后两个参数都是32位的消息的附加信息参数 //程序本身通常自己不呼叫窗口消息处理程序,而由系统呼叫窗口消息处理函数 //通过SendMessage()函数可以实现程序自己呼叫窗口消息处理函数 //Windows程序写作者使用switch和case结构处理消息; 窗口消息处理程序在处理消息时,必须回传一个0. //窗口消息处理程序不予处理的所有消息应该被传给名为DefWindowProc的Windows函数,而且从DefWindowProc传回的值必须由窗口消息处理程序传回 { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message) { //case WM_CREATE: case WM_PAINT: hdc=BeginPaint(hwnd,&ps); GetClientRect(hwnd,&rect); DrawText(hdc,TEXT("Hello win"),-1,&rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd,&ps); return 0; //消息处理完后必须返回0值到系统 case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); //这个函数的返回值必须由窗口消息处理程序返回到系统 }
执行上面的程序需要建立一个win32 应用程序工程,最好是个空的工程, 建议在VC 6.0中执行。