我准备从头开始好好学学MFC原理及应用,先从Win API开始学起,不仅要会用,知其然更要知其所以然,这样才能走的更远。
先讲一下Windows应用程序的消息机制,Windows 程序的进行是依靠外部发生的事件来驱动。程序循环等待输入,然后做出适当的处理。输入分为两类:一类是由硬件(鼠标键盘)产生的消息,加入系统消息队列(system queue)中;一类是由windows系统或其他windows程序传来的消息,加入程序消息队列(application queue)中。
比如我们点击鼠标,会产生一个硬件输入,USER模块捕捉到一个硬件事件,生成一个对应的消息加入系统消息队列中,Windows程序获取到该消息,翻译并接住USER模块分发给窗口过程并加以处理,这就是Windows程序消息机制的整个流程。
下面我来给大家展示一个简单的例子。
1、创建空的Windows项目,添加一个cpp文件,并包含windows.h头文件
#include <windows.h>
// 添加两个全局变量,方便使用
HINSTANCE _hInst; // instance 句柄
HWND _hWnd; // 窗口句柄
2、实现Windows程序的入口函数
//在控制台程序中,入口函数是main函数
//但在windows程序中,入口函数是WinMain函数
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
// 注册窗口类
if(!InitApplication(hInstance)) return FALSE;
// 创建并显示窗口类
if(!InitInstance(hInstance, nShowCmd)) return FALSE;
// 循环获取消息
MSG msg;
while(GetMessage(&msg, NULL/*NULL表示捕捉所有窗口的消息*/, 0, 0)) {
TranslateMessage(&msg); //翻译消息,对于组合键消息,需要先翻译,而不是分开分发
DispatchMessage(&msg); //分发消息
}
return 0;
}
3、注册窗口
BOOL InitApplication( HINSTANCE hInstance )
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW; //窗口显示风格
wc.lpfnWndProc = (WNDPROC)WndProc; // 窗口消息处理函数
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance; //应用程序实例句柄
wc.hIcon = LoadIcon(NULL/使用系统默认的图标/, IDI_ERROR); // 图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标样式
wc.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH); // 窗口背景颜色
wc.lpszClassName= TEXT(“Test”); //窗口类名称
wc.lpszMenuName = TEXT(“TestMenu”); // 菜单资源名称
return RegisterClass(&wc); // 注册窗口
}
4、创建+显示窗口
BOOL InitInstance( HINSTANCE hInstance, int nShowCmd )
{
_hInst = hInstance;
_hWnd = CreateWindow(
TEXT("Test"), //类名,注意此处类名必须和加载时的类名一致
TEXT("TestWnd"), //窗口名称
WS_OVERLAPPEDWINDOW, //窗口类型,边框,最大最小化等
CW_USEDEFAULT, //窗口位置和大小
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL, //父窗口柄
NULL, //菜单句柄
hInstance, //与窗口关联的模块实例的句柄
NULL
);
if(!_hWnd) return FALSE;
ShowWindow(_hWnd, nShowCmd); //显示窗口
UpdateWindow(_hWnd); //发送WM_PAINT消息
return TRUE;
}
5、循环获取消息并分发
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
/*
Queued message structure
typedef struct tagMSG
{
HWND hwnd;
UINT message; // WM_xxx,例如WM_MOUSEMOVE,WM_SIZE…
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
*/
//循环获取消息
MSG msg;
while(GetMessage(&msg, NULL/NULL表示捕捉所有窗口的消息/, 0, 0)) {
TranslateMessage(&msg); //翻译消息,对于组合键消息,需要先翻译,而不是分开分发
DispatchMessage(&msg); //分发消息
}
}
这部分代码在入口函数中已经有,但是这里在提出来强调一下。GetMessage函数循环从消息队列中获取消息并翻译分发,接下来便是处理消息。
6、窗口过程,处理消息
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
int wmId, wmEvent;
switch(message) {
case WM_COMMAND: //命令消息,对应控件产生的消息
wmId = LOWORD(wParam); //分离出低位的控件ID
wmEvent = HIWORD(wParam); //高位的事件类型
break;
case WM_CLOSE: //右上角的x,关闭窗口
DestroyWindow(hWnd); //仅关闭窗口,没有关闭进程,并发送另一个消息WM_DESTROY
break;
case WM_DESTROY: //摧毁窗口消息
PostQuitMessage(0); //真正的关闭进程
break;
case WM_LBUTTONDOWN:
{
//鼠标左键按下时,显示当前坐标
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
char buff[1024];
wsprintf(buff, TEXT("X = %d, Y = %d"), xPos, yPos);
MessageBox(hWnd, buff, TEXT("鼠标左键按下"), MB_OK);
break;
}
case WM_PAINT: //在窗口绘制文字
{
PAINTSTRUCT ps; //绘图结构体
HDC hdc = BeginPaint(hWnd, &ps); //画家
TextOut(hdc, 100, 100, TEXT("HELLO"), strlen("HELLO"));
EndPaint(hWnd, &ps);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
到这里,一个简单的Windows应用程序便编写完成了,由于没有添加控件,控件消息处理没有详细说明,后续补足。
大家可以尝试运行代码看看,结合注释一起理解一下。
参考文献:
《深入浅出MFC》侯捷版