GUI应用程序的入口函数是 WinMain , 这是一个自定义的入口函数。WinMain函数采用的是windows标准调用方式。下面是简单的windows程序:
#include
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
::MessageBox(nullptr, TEXT("Hello, Win32 Application"), TEXT("First Win32 Window"), MB_OK);
return 0;
}
系统传给WinMain函数几个参数定义如下:
hInsatnce = (HINSTANCE)GetModuleHandle(nullptr);
GetModuleHandle 函数的唯一参数是模块名称,函数返回这个模块句柄。如果传递nullptr的话函数返回可执行文件所在模块的模块句柄。
当Windows向程序发送消息时,他调用程序的一个函数,这个函数的参数精确的描述了Windows发送的消息。在程序中称为窗口函数或消息处理函数。它是一个自定义的回掉函数,原型如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
hwnd 标识了消息的到达窗口
uMsg 参数时一个被命名的常量(消息ID)
下面是一个简单示例
#include
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd = ::FindWindow(nullptr, TEXT("无标题 - 记事本"));
if (hWnd != nullptr)
::SendMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
}
FindWindow 函数会查找窗口类名和窗口标题与指定字符串相匹配的窗口。返回找到的窗口句柄;SendMessage 用于向窗口中发送消息。
下面是创建一个窗口的完整代码
#include
#include
LRESULT CALLBACK MainWindProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wndClass;
wndClass.cbSize = sizeof(wndClass);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = MainWindProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = TEXT("MainWClass");
wndClass.hIconSm = nullptr;
// 注册窗口类
::RegisterClassEx(&wndClass);
// 创建窗口
HWND hwnd = ::CreateWindowEx(\
0, \
TEXT("MainWClass"), \
TEXT("My First Window"), \
WS_OVERLAPPEDWINDOW, \
CW_USEDEFAULT, \
CW_USEDEFAULT, \
CW_USEDEFAULT, \
CW_USEDEFAULT, \
nullptr, \
nullptr, \
hInstance, \
nullptr);
if (hwnd == nullptr)
{
std::cout << "Create Window Error!" << std::endl;
return -1;
}
// 显示窗口
::ShowWindow(hwnd, nCmdShow);
// 刷新窗口客户区
::UpdateWindow(hwnd);
MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK MainWindProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = ::BeginPaint(hwnd, &ps);
std::wstring str = L"This is Test Text!";
::TextOut(hdc, 0, 0, str.c_str(), str.size());
::EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
上面的代码是创建一个窗口程序的一般步骤,具体流程如下:
注册窗口类使用函数RegisterClassEx,一个窗口类定义了窗口的一些主要属性,如图标、光标、背景色和负责处理消息的窗口函数等。这些属性定义在WNDCLASSEX结构中。
typedef struct _WNDCLASSEX{
UINT cbSize; // WNDCLASSEX结构的大小
UINT style; // 从窗口派生的窗口具有的风格
WNDPROC lpfnWndProc; // 消息处理函数指针
int cbClsExtra; // 指定紧跟在窗口类结构后的附加字节数
int cbWndExtra; // 指定紧跟在窗口示例后的附加字节数
HINSTANCE hInstance; // 本模块的实例句柄
HICON hIcon; // 窗口左上角图标的句柄
HCURSOR hCursor; // 光标的句柄
HBRUSH hbrBackground; // 背景画刷的句柄
LPCSTR lpszMenuName; // 菜单名
LPCSTR lpszClassName; // 该窗口类的名称
HICON hIconSm; // 小图标句柄
}WNDCLASSEX;
wndClass.style = CS_HREDRAW | CS_VREDRAW; // 指定如果大小改变就重画
前缀CS_意为class style,在WINUSER.H中定义了全部可选样式。
#define CS_VREDRAW 0x0001
#define CS_HREDRAW 0x0002
#define CS_DBLCLKS 0x0008
#define CS_OWNDC 0x0020
#define CS_CLASSDC 0x0040
#define CS_PARENTDC 0x0080
#define CS_NOCLOSE 0x0200
#define CS_SAVEBITS 0x0800
#define CS_BYTEALIGNCLIENT 0x1000
#define CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000
wndClass.lpfnWndProc = MainWindProc;
WNDCLASSEX 结构成员lpfnWndProc 指定了基于此类窗口的窗口函数。当窗口收到消息时Windows即自动调用这个函数通知应用程序。
wndClass.hInstance = hInstance;
wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
LoadIcon 函数装载了一个预定义图标,命名为 IDI_APPLICATION
LoadCursor 函数装载了一个预定义的光标,命名为 IDC_ARROW
wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); // 使用白色画刷
wndClass.lpszClassName = TEXT("MainWClass"); // 窗口类名称
填充完WNDCLASSEX 结构,就可以进行注册了。RegisterClassEx 函数调用失败将返回0
::RegisterClassEx(&wndClass);
要创建窗口,使用函数 CreateWindowEx 。
HWND hwnd = ::CreateWindowEx(\
0, \ // dwExStyle, 扩展样式
TEXT("MainWClass"), \ // 类名
TEXT("My First Window"), \ // 标题
WS_OVERLAPPEDWINDOW, \ // 窗口风格
CW_USEDEFAULT, \ // X, 初始X坐标
CW_USEDEFAULT, \ // Y, 初始Y坐标
CW_USEDEFAULT, \ // nWidth, 宽度
CW_USEDEFAULT, \ // nHeight, 高度
nullptr, \ // hWndParent, 父窗口句柄
nullptr, \ // hMenu,菜单句柄
hInstance, \ // hInstance, 程序实例句柄
nullptr); // lpParam, 用户数据
函数调用成功将返回窗口句柄,失败返回nullptr。
第四个参数dwStyle的值是 WS_OVERLAPPEDWINDOW,即重叠窗口。由他指定的窗口有标题栏、系统菜单、可以改变大小的边框,以及最大化、最小化和关闭按钮。这是一个标准的窗口样式。下面是一些常见风格定于:
::ShowWindow(hwnd, nCmdShow);
ShowWindow 函数用于指定窗口的显示状态。nCmdShow 取值可以是 SW_SHOW 、SW_HIDE、SW_MINIMIZE
::UpdateWindow(hwnd);
如果指定窗口的更新区域不为空的话,UpdateWindow 函数通过向这个窗口发送一个WM_PAINT 消息更新它的客户区。当窗口显示在屏幕上时,窗口的客户区被 WNDCLASSEX 中指定的刷子擦去,调用 UpdateWindow 函数将促使客户区重画,以显示其内容。
Windows为每一个线程维护一个消息队列,每当有一个输入发生,Windows就把用户输入翻译成消息放在消息队列中。GetMessage 函数可以从消息队列中取一个消息填充MSG结构。
如果消息队列中没有消息,这个函数会一直等下去,直到有消息进入消息队列为止。
typedef struct tagMSG {
HWND hwnd; // 消息要发向的窗口句柄
UINT message; // 消息标识符,以WM_开头的预定义值(Windows Message)
WPARAM wParam; // 消息的参数之一
LPARAM lParam; // 消息的参数之二
DWORD time; // 消息放入消息队列的时间
POINT pt; // 这是一个POINT数据结构,表示消息放入消息队列时鼠标的位置
} MSG,
GetMessage 函数从消息队列中取得的消息如果不是WM_QUIT ,则返回非零值。一个WM_QUIT 消息会使GetMessage 函数返回0,从而消息循环结束。
::TranslateMessage(&msg);
此调用把键盘输入翻译成可调用的消息。
::DispatchMessage(&msg);
DispatchMessage 函数分发一个消息到对应窗口的窗口函数。MainWndProc 处理消息后把控制权交给Windows,此时DispatchMessage 函数仍然继续工作,当他返回时,消息队列从调用GetMessage 函数开始进入下一轮。
所有消息不做处理的消息都必须返回一个名为DefWindowProc的函数让Windows做默认处理,从DefWindowProc 函数返回的值也必须从消息处理函数返回。
每当客户区变为无效,消息处理函数就会收到一个新的WM_PAINT 消息。
WM_DESTORY 是窗口必须处理的一个消息,当用户关闭窗口时,消息处理函数就会收到一 个 WM_DESTORY消息。当接收到这个消息的时候,说明窗口正在销毁。
::PostQuitMessage(0);
PostQuitMessage函数会会向消息队列中插入一个 WM_QUIT 消息。GetMessage 函数如果从消息队列中获得到消息时 WM_QUIT ,它将返回0。从而退出消息循环。如果不使用函数 PostQuitMessage 发送WM_QUIT 消息,则界面虽然被销毁了,但是消息循环还在继续,程序还没有结束。