ZetCode

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 指定窗口的初始水平和垂直位置。nWidthnHeight 指定窗口的宽度和高度。

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 是消息。wParamlParam 参数提供额外的消息信息。这些参数的值取决于消息类型。

消息来自用户或操作系统。我们对消息做出反应,或者我们调用默认窗口过程以提供默认处理。大多数消息被发送到默认窗口过程。默认窗口过程被称为 DefWindowProcW。它使用与普通窗口过程相同的参数调用。

一个简单的窗口

以下示例显示了一个骨架 Windows 应用程序。

simplewindow.c
#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.libuser32.libgdi32.lib——并通过包含 <windows.h> 头文件,将 Windows API 添加到 C 编程项目中。

wc.style = CS_HREDRAW | CS_VREDRAW;

我们在这里设置窗口样式。CS_HREDRAWCS_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 消息做出反应。PostQuitMessageWM_QUIT 消息发送到消息队列。所有其他消息都使用 DefWindowProcW 函数发送到默认处理。

A window
图:一个窗口

在本部分 Windows API 教程中,我们创建了一个基本窗口。