从0开始,Win32API编程

从环境搭建——第一个窗口程序——执行第一个自定义窗口

Windows API编程,之所以晦涩难懂,就是各种自带的变量名和奇怪的宏层出不穷;因此,我认为最好的学习路线就是从官方示例的代码开始学起,遇到了一个不懂的概念再去查找资料,这样子学习才事半功倍

0、开发环境搭建

参考链接: VS创建空的Win32程序

VS 的窗口程序模板太多太杂了,并不合适初学者去理解各个API的作用

空项目比较干净,缺啥补啥,也不会把代码看的眼花缭乱

1、创建第一个Windows 窗口程序

代码如下:

#ifndef UNICODE
#define UNICODE
#endif 

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 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) > 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);

        // All painting occurs here, between BeginPaint and EndPaint.

        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));

        EndPaint(hwnd, &ps);
    }
    return 0;

    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

接下来我们对以上的代码进行抽丝剥茧

上段代码中有两个函数,分别是

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
    
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

1.1 wWinMain

我们先来介绍一下wWinMain函数

wWinMain 是一种 Windows 程序的入口函数,用于运行 Windows 图形应用程序, 专门用于支持 Unicode 字符集的。就跟我们学C语言,main函数的作用类似

知道程序的入口在这里,那我们先来看第一个代码块

 // 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);

从注释知道,这一个代码块起到注册窗口类的作用,在Java里面就是定义一个类

  1. const wchar_t CLASS_NAME[] = L"Sample Window Class";:这一行定义了一个名为CLASS_NAME的常量字符数组,存储了窗口类的名称。窗口类是窗口的模板,用于定义窗口的各种属性和行为。
  2. WNDCLASS wc = { };:这一行创建了一个WNDCLASS结构体的实例,它用于描述窗口类的属性。通过初始化为零,确保结构体的所有成员都被设置为默认值。
  3. wc.lpfnWndProc = WindowProc;:这一行设置了窗口类的窗口过程,窗口过程是一个函数,用于处理窗口接收到的消息。在这里,WindowProc函数被分配为窗口过程。
  4. wc.hInstance = hInstance;:这一行设置窗口类的实例句柄。hInstance是在wWinMain函数中传递的应用程序实例句柄,它表示了应用程序的唯一标识。
  5. wc.lpszClassName = CLASS_NAME;:这一行设置了窗口类的名称,使用之前定义的CLASS_NAME常量。
  6. RegisterClass(&wc);:最后,通过调用RegisterClass函数,窗口类被注册到Windows操作系统中。这将在系统中创建一个窗口类实例,以便后续可以使用这个窗口类来创建窗口。

再来看第二个代码块

// 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);

注释的意思是这个代码块用来创建窗口的实例,在Java里面就是新建一个对象(实例)

这段代码用于创建一个窗口实例,设置窗口的属性和显示窗口。让我详细解释其中的各个部分:

  1. HWND hwnd = CreateWindowEx(...);:这一行使用CreateWindowEx函数创建一个窗口实例,并将返回的窗口句柄存储在hwnd变量中。以下是CreateWindowEx函数的参数解释:
    • 0:这是可选的窗口样式,可以用来设置窗口的外观和行为。在这里,使用0表示没有特殊的窗口样式。
    • CLASS_NAME:这是之前注册的窗口类的名称,它指示要创建哪个类型的窗口。
    • L"Learn to Program Windows":这是窗口的标题栏文本,显示在窗口的标题栏上。
    • WS_OVERLAPPEDWINDOW:这是窗口的样式,指定了窗口的外观和行为,包括标题栏、边框、最小化和最大化按钮等。WS_OVERLAPPEDWINDOW是一个常用的窗口样式,通常表示标准的可调整大小和可移动的窗口。
    • Size and position:这些参数定义了窗口的初始大小和位置。在这里,CW_USEDEFAULT表示使用默认值,通常会根据窗口样式自动确定窗口的大小和位置。
    • NULL:这是可选的父窗口句柄,通常用于创建子窗口。在这里,我们创建的是顶层窗口,所以父窗口设置为NULL
    • NULL:这是可选的菜单句柄,用于指定与窗口关联的菜单。在这里,我们没有指定菜单,所以设置为NULL
    • hInstance:这是应用程序实例句柄,表示应用程序的唯一标识。它是在wWinMain函数中传递的。
    • NULL:这是可选的附加应用程序数据指针。通常情况下,可以将其设置为NULL
  2. if (hwnd == NULL):这是一个错误检查,它检查窗口是否成功创建。如果创建窗口失败,窗口句柄hwnd将为NULL,然后程序会在此处返回0,表示初始化失败或发生了错误。
  3. ShowWindow(hwnd, nCmdShow);:一旦窗口成功创建,使用ShowWindow函数将窗口显示在屏幕上。nCmdShow参数表示窗口显示的方式,通常由操作系统传递给应用程序的wWinMain函数。

再来看第三个代码块

// Run the message loop.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;

注释的意思是该代码块进入消息循环,就像JavaScript中的监听事件

  1. MSG msg = { };:首先,定义一个MSG结构体的实例,用于接收消息。
  2. while (GetMessage(&msg, NULL, 0, 0) > 0):这是一个无限循环,它使用GetMessage函数来不断获取消息,直到获取到一个表示退出应用程序的消息(通常是WM_QUIT)为止。GetMessage函数的参数如下:
    • &msg:这是一个指向MSG结构体的指针,GetMessage函数会将接收到的消息信息填充到这个结构体中。
    • NULL:这表示消息的来源窗口。通常,这里使用NULL表示接收来自所有窗口的消息。
    • 00:这是消息的最小值和最大值,通常使用0表示接收所有消息,不做范围限制。
  3. TranslateMessage(&msg):在消息循环中,通常在获取消息后,会调用TranslateMessage函数。这个函数用于处理特定的消息,如键盘输入消息,以便 Windows 可以正确识别并处理它们。在大多数情况下,你会在消息循环中调用它,但不需要关心其具体细节,因为它会自动处理。
  4. DispatchMessage(&msg):这是消息循环的核心部分。它用于将接收到的消息分派到适当的窗口过程中,以便窗口处理消息。每个窗口都有一个关联的窗口过程,用于处理特定消息。这个函数将消息传递给相关窗口过程,以便窗口可以响应消息。
  5. return 0:一旦收到表示退出应用程序的消息(通常是WM_QUIT消息),消息循环结束,程序退出。这时返回0表示正常退出。

1.2 WindowProc

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);
        // All painting occurs here, between BeginPaint and EndPaint.
        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
        EndPaint(hwnd, &ps);
    }
    return 0;

    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

这是一个窗口过程,用于处理窗口消息。这个窗口过程通常与 Windows 窗口关联,负责响应不同类型的消息。

  1. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam):这是一个窗口过程的定义。它接受四个参数:
    • hwnd:表示窗口句柄,是与窗口关联的唯一标识。
    • uMsg:表示窗口消息的整数标识,不同的 uMsg 值对应不同类型的消息。
    • wParamlParam:这两个参数包含了与消息相关的附加信息,具体的含义取决于消息类型。
  2. switch (uMsg):窗口过程使用 switch 语句根据不同的消息类型 uMsg 执行不同的操作。
    • WM_DESTROY:当窗口接收到销毁消息时,通常表示用户关闭了窗口,它会执行以下操作:
      • PostQuitMessage(0):这个函数会向消息队列中发送一个 WM_QUIT 消息,通知应用程序退出。这通常会导致消息循环中的 GetMessage 函数返回 0,从而结束应用程序。
      • return 0:在处理销毁消息后,返回 0 表示消息已经被处理。
    • WM_PAINT:当窗口需要进行绘制操作时,通常表示窗口部分区域需要重新绘制,执行以下操作:
      • PAINTSTRUCT ps:创建一个 PAINTSTRUCT 结构体,用于存储绘制信息。
      • HDC hdc = BeginPaint(hwnd, &ps):获取绘图设备上下文(HDC),以便进行绘制操作。
      • BeginPaintEndPaint 之间进行绘制操作。在这里,它使用 FillRect 来填充窗口客户区域的背景颜色。FillRect 函数用于填充矩形区域的颜色。
      • EndPaint(hwnd, &ps):结束绘制过程,释放资源。
      • return 0:在处理绘制消息后,返回 0 表示消息已经被处理。
  3. return DefWindowProc(hwnd, uMsg, wParam, lParam):如果消息不是上述两种之一,窗口过程会通过 DefWindowProc 函数将消息传递给默认窗口过程,以确保窗口的基本行为正常运作。这是一种通常的做法,以确保未明确处理的消息能够被正确处理。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值