通过以下代码可以创建一个简单的窗口应用程序,我们通过解释以下代码来了解WinAPI编程
#ifndef UNICODE
#define UNICODE
#endif
#include
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
WindowProc
,在案例中定义了行为和界面和如何与用户的交互注意的是
WindowProc
并不会显示的调用,而是通过Windows的一系列消息和DispatchMessage
函数来分派每个消息去间接的调用WindowProc
,每个消息都会调用一次
windows事件可以发生在任何时刻且顺序基本一致,如何构造一个不能预先预测其执行流程的程序,为此,Windows使用消息传递模型,操作系统传递消息给应用程序,一个消息是一个简单地数字来代表一个特定的消息,当消息传递到窗口,操作系统将调用这个窗口所注册的窗口过程
一个应用程序将会接收很多很多的消息,此外一个应用程序也有许多许多窗口,那么我们如何在编写程序时怎么正确的获取消息以及调用给正确的窗口呢,显然应用程序需要循环去检索消息并且派发他们给正确的窗口
在创建窗口的每个线程上,操作系统都为了窗口消息创建了一个队列,在创建这个窗口的线程上这个队列持有这个窗口的所有消息,这个队列是隐藏在你的程序中的,你不能直接操作它,但是你可以通过GetMessage
函数从此处拉取数据,例如
MSG msg; // 得到的消息,可能也没有
GetMessage(&msg, NULL, 0, 0);
这个方法移出队列顶端的第一个消息,这个方法将会阻塞,直到有另一个消息被加入队列为止,实际上GetMessage
不会让你的程序变的无响应,如果队列中没有消息,那么程序什么都不会做,如果你有执行后台程序,你可以创建在GetMessage
等待另一条消息时继续运行的其他线程
GetMessage
函数的第一个参数是MSG
结构体的地址,如果执行成功,那么将会在MSG
内填充关于消息的信息(包括目标窗口和消息码),剩下三个参数可以使你在从队列获取消息时进行过滤,但在通常情况下,都会置空
即使MSG
包含着关于消息的信息,但是你通常不会直接使用它,而是将其传给两个函数
TranslateMessage(&msg);
DispatchMessage(&msg);
TranslateMessage
方法是与键盘等输入相关的,它转换键盘等输入到字符,你不需要去关系这个是如何工作的,只需要去记得在DispatchMessage
之前调用就行
DispatchMessage
函数告诉操作系统调用作为消息目标的窗口的窗口过程,换句话说,操作系统在窗口表中查找窗口句柄,找到函数指针相关联的窗口并且那个方法
当窗口过程返回时,返回到DispatchMessage
函数,接着到消息循环的下一个消息
,通常情况下GetMessage
返回非0值,当你想退出程序并结束循环时可以调用PostQuitMessage(0)
方法,这个方法会将WM_QUIT
消息放入栈中,WM_QUIT
是一个特殊的消息,它会引起GetMessage
返回0,从而判断退出循环结束程序
DispatchMessage
函数调用消息目标的窗口的窗口过程,而这个窗口过程的签名必须如下
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
关于这四个参数:
CALLBACK
是函数的约定而已如果你没有在窗口过程中处理特定的消息,可以通过调用DefWindowProc
函数来执行对特定消息的处理
return DefWindowProc(hwnd, uMsg, wParam, lParam);
有时你的程序启动绘图来更新窗口的外观,在其他时候操作系统将会通知你让你必须重绘窗口的一部分,当这个窗口重现时,操作系统发送一个WM_PAINT
消息,必须绘制的窗口部分称为更新区域
窗体首次显示,这个窗口的全部客户区域必须被绘制,因此,当你显示一个窗口时,你总是会收到至少一条WM_PAINT
消息
你只要负责绘制客户区域,其它框架部分有操作系统完成,在你绘制完成之后你将清理更新区域,这将告诉操作系统在某些被改变之前不需要发送其它的WM_PAINT
消息
现在支持用户移动窗口,那么有可能窗口的一部分会被隐藏,当隐藏的部分再次显示时,这个被隐藏的部分将会进入更新区域,你的窗口将收到另一个WM_PAINT
消息
如果用户拉伸收缩窗口,那么更新区域也会被改变,当用户往右拉伸时,窗口右侧新暴露的区域被添加到更新区域
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 所有绘制发送在此,位于BeginPaint和EndPaint之间
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
通过调用BeginPaint
函数来开始绘制的操作,这个方法将会填充信息和绘制请求到PAINTSTRUCT
结构,更新区域由PAINTSTRUCT
结构的rcPaint
成员给出,此更新区域是相对于客户区域定义的,绘制完成后,调用EndPaint
函数。此函数清除更新区域,该区域向窗口发出窗口本身已完成绘制的信号