Windows API 中的窗口
最后修改于 2023 年 10 月 18 日
窗口是屏幕上的一个矩形区域,应用程序在其中显示输出并从用户接收输入。在 Windows 中,一切都是窗口。至少从程序员的角度来看是这样的。一个主窗口、一个按钮、一个静态文本,甚至一个图标;它们都是窗口。静态文本只是窗口的一种特殊类型,桌面区域也是如此。
wWinMain 函数
每个 Windows UI 应用程序必须至少有两个函数:WinMain 函数和窗口过程。WinMain 函数是 Windows UI 应用程序的入口点。它初始化应用程序,在屏幕上显示应用程序窗口,并进入主循环。在我们的示例中,我们使用 wWinMain 函数原型,该原型用于创建 Unicode UI 程序。
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow);
hInstance 是一个实例句柄。它是一个 32 位数字,用于标识我们的程序在操作系统环境中的实例。这个数字是由 Windows 在程序开始执行时提供的。hPrevInstance 参数始终为 NULL;它是 16 位 Windows 的遗留产物。Windows 程序也可以从命令行启动。给定的参数存储在 pCmdLine 参数中。nCmdShow 值指定窗口将如何显示:最小化、最大化或隐藏。
当它接收到 WM_QUIT 消息时,wWinMain 函数终止。
注册窗口类
在创建窗口之前,我们必须在 Windows 中注册其类。许多控件已经注册了它们的窗口类。因此,当我们创建一个按钮或静态文本时,我们不需要为它们注册窗口类。要注册窗口类,我们必须创建并填充一个 WNDCLASS 结构。我们设置窗口样式、额外分配的字节数、窗口类名、程序实例的句柄、背景画刷、可选的菜单名、窗口过程、光标的句柄和一个图标。然后调用 RegisterClassW 函数。
创建窗口
通过调用 CreateWindowW 函数创建窗口。
HWND CreateWindowW(LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
lpClassName 唯一地标识窗口。它是我们注册窗口的名称。lpWindowName 是窗口名称。它的作用取决于上下文——它可以是父窗口中的窗口标题,也可以是子窗口(如按钮或静态文本)中的标签。可以使用多种样式创建 Windows。为此,我们有 dwStyle 参数。x, y 指定窗口的初始水平和垂直位置。nWidth 和 nHeight 指定窗口的宽度和高度。
hWndParent 是父窗口的句柄。对于没有父窗口的窗口,我们使用 NULL。对于父窗口,hMenu 是一个可选的菜单句柄,对于子窗口,它是一个控件标识符。hInstance 是程序实例的句柄。lpParam 是最后一个参数,它是在 WM_CREATE 消息期间传递给窗口的可选值。CreateWindowW 函数返回新创建窗口的句柄。
消息
WinMain 函数创建一个消息循环。它是一个在应用程序生命周期中运行的无限循环。消息循环是一种编程结构,它等待并分发程序中的事件或消息。Windows 使用消息进行通信。一个消息是一个整数值,用于标识一个特定的事件——按钮点击、窗口大小调整或应用程序关闭。
可以在同一时刻创建多个消息。这些消息不能同时处理;因此它们存储在消息队列中。消息进入消息队列并等待被处理。GetMessage 函数从消息队列中检索消息。DispatchMessage 函数将消息分发给窗口过程。如果应用程序获取字符输入,我们在循环中包含 TranslateMessage 函数。
窗口过程
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
每个窗口都有一个关联的窗口过程。它是一个接收消息的函数。hwnd 是将要接收消息的窗口的句柄。uMsg 是消息。wParam 和 lParam 参数提供额外的消息信息。这些参数的值取决于消息类型。
消息来自用户或操作系统。我们对消息做出反应,或者我们调用默认窗口过程以提供默认处理。大多数消息被发送到默认窗口过程。默认窗口过程被称为 DefWindowProcW。它使用与普通窗口过程相同的参数调用。
一个简单的窗口
以下示例显示了一个骨架 Windows 应用程序。
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow) {
MSG msg;
HWND hwnd;
WNDCLASSW wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.lpszClassName = L"Window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpszMenuName = NULL;
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 350, 250, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
我们将逐步解释示例代码。
#include <windows.h>
这是一个 C 编程语言的头文件。它包含 API 中的所有函数声明、所有常用宏和所有数据类型。通过链接必要的库——kernel32.lib、user32.lib、gdi32.lib——并通过包含 <windows.h> 头文件,将 Windows API 添加到 C 编程项目中。
wc.style = CS_HREDRAW | CS_VREDRAW;
我们在这里设置窗口样式。CS_HREDRAW 和 CS_VREDRAW 标志表示,每当窗口的高度或宽度发生移动或调整大小时,将重绘整个窗口。
wc.cbClsExtra = 0; wc.cbWndExtra = 0;
在我们的示例中,我们不使用额外的字节。因此,我们将成员设置为零。这两个属性最常见的用途是窗口子类化。
wc.lpszClassName = L"Window";
窗口是这种特定窗口类型的类名。我们在创建窗口时使用此类名。L 字符位于宽字符串之前。
wc.hInstance = hInstance;
我们设置程序的实例。
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
我们在这里设置背景画刷。它用于绘制窗口的客户区。
wc.lpszMenuName = NULL;
在我们的示例中,我们不创建菜单。
wc.lpfnWndProc = WndProc;
我们为窗口类提供窗口过程。
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
我们为我们的应用程序设置光标。我们使用 LoadCursor 函数从系统资源加载光标。IDC_ARROW 是标准箭头光标的值。
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
我们为我们的应用程序设置图标。该图标使用 LoadIcon 函数从系统资源中检索。IDI_APPLICATION 是默认应用程序图标的值。
RegisterClassW(&wc);
我们使用系统注册窗口类。
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
这两行在屏幕上显示窗口。nCmdShow 指定我们在屏幕上显示窗口的方式。
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
这是消息循环。我们使用 GetMessage 函数从消息队列接收消息,并使用 DispatchMessage 函数将它们分发给窗口过程。
return (int) msg.wParam;
在应用程序的末尾,将退出代码返回给系统。
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
在窗口过程中,我们对 WM_DESTROY 消息做出反应。PostQuitMessage 将 WM_QUIT 消息发送到消息队列。所有其他消息都使用 DefWindowProcW 函数发送到默认处理。
在本部分 Windows API 教程中,我们创建了一个基本窗口。