• Win32 - 窗口


    Win32 - 窗口



    前言

    对于广大的玩家而言,Windows是一个不错的平台选择。特别是DirectX12展现的卓越性能使得大多大型游戏由其开发。但DX12只能在Win上运行,所以学习Win32API是必要的。

    流程图


    )

    创建项目

    VS

    Create a new Project > Windows Desktop Application

    MinGW

    myWin32App:
    g++ win32_window.cpp -o myWin32App -g -Wall -lgdi32 #-mwindow
    • 一定要链接gdi32

    • 如果以main为入口点,不想要黑框框就加上-mwindow选项

    Win32API字符串

    Typedef 定义
    CHAR char
    PSTR 或 LPSTR char*
    PCSTR 或 LPCSTR const char*
    PWSTR 或 LPWSTR wchar_t*
    PCWSTR 或 LPCWSTR const wchar_t*

    Unicode 和 ANSI 函数

    当 Microsoft 为Windows引入 Unicode 支持时,它通过提供两组并行 API(一个用于 ANSI 字符串,另 一个用于 Unicode 字符串)来缓解转换。 例如,有两个函数用于设置窗口标题栏的文本:

    SetWindowTextW(); // Unicode function with wide-character string.
    SetWindowTextA(); // ANSI function.

    在内部,ANSI 版本将字符串转换为 Unicode。 Windows标头还定义了一个宏,该宏在定义预处理器符号 UNICODE或 ANSI 版本时解析为 Unicode 版本。

    ----Microsoft Docs

    为了适应国际化编程,我们将使用Unicode编码。

    #ifdef UNICODE
    #define SetWindowText SetWindowTextW
    #else
    #define SetWindowText SetWindowTextA
    #endif

    定义UNICODE以后,我们就不必为用W还是A担心了。但要注意,入口点仍是wWinMain

    TCHAR

    在c++中L“xxx”是定义宽字符,其对应类型是wchar_t

    中对其做了定义:

    /****/
    typedef wchar_t WCHAR;
    typedef WCHAR TCHAR, *PTCHAR;
    #define TEXT(quote) __TEXT(quote)
    #define __TEXT(quote) L##quote
    Unicode ANSI
    TCHAR wchar_t char
    TEXT (“x”) L"x" "x"

    WinMain:Win32 Application入口点函数

    每个完整的C++程序都有自己的入口点函数,例如最常用的C++ Console入口点main。

    Win32api 有自己的入口点函数WinMainwWinMain,他和C++ Console看起来如下:

    int main(int argc,char* argv[]);//Console entry-point function.
    int WINAPI wWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    PWSTR pCmdLine,
    int nCmdShow);//Win32 Application entry-point function.

    我们下面来解释下参数:

    • hInstance 称为“实例句柄”。用于标识可执行文件。 某些Windows函数需要实例句柄。
    • hPrevInstance 没有意义。 它在 16 位Windows中使用,但现在始终为零。我们不管他。
    • pCmdLine 包含命令行参数。就像Console中的argv一样.
    • nCmdShow 是一个标志,指示主应用程序窗口是最小化、最大化还是正常显示。

    Console下创建窗口

    如果你不想用WinMain作为入口点而用main其实也可以,其实很多库,比如GLFW就是可以让你在console下创建win32窗口的。

    我们可以通过获取Console的窗口句柄然后通过GetWindowLong获得实例句柄创建Win32窗口,具体代码看起来如下:

    HWND hConWnd = GetConsoleWindow();
    HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hConWnd, GWLP_HINSTANCE);

    窗口类

    窗口类是一个属性集,是Windows编程中用于创建窗口的模板。每一个窗口类都有一个WndProc,负责处理发送该类窗口的所有消息。

    下面让我们来看一个例子:

    // 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;
    • lpfnWndProc 是指向名为 窗口过程 或“window proc”的应用程序定义函数的指针。窗口过程定义窗口的大部分行为。 稍后我们将详细检查窗口过程。 目前,只需将此视为转发引用。
    • hInstance是应用程序实例的句柄。 从 wWinMain 的 hInstance 参数获取此值。
    • lpszClassName 是标识窗口类的字符串。

    类名是当前进程的本地名称,因此该名称仅在进程内唯一。 但是,标准Windows控件也有类。 如果使用这些控件中的任何一个,则必须选取与控件类名不冲突的类名。 例如,按钮控件的窗口类名为“Button”。

    以上3个成员是必须要有的,还有一些其他的成员:

    typedef struct tagWNDCLASSW {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCWSTR lpszMenuName;
    LPCWSTR lpszClassName;
    } WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
    #ifdef UNICODE
    typedef WNDCLASSW WNDCLASS;
    //...
    #endif

    这些我们以后再讲,有兴趣可以参见

    WNDCLASSW (winuser.h) - Win32 apps | Microsoft Docs

    注册窗口类

    WNDCLASS结构的地址传递给RegisterClass函数。 此函数向操作系统注册窗口类。

    RegisterClass(&wc);

    创建窗口

    创建窗口我们可以调用CreateWindowCreateWindowEx第二个会多一个扩展参数。

    我们一般用第二个:

    HWND hwnd = CreateWindowEx(
    0, // Optional window styles.
    CLASS_NAME, // Window class
    L"My Application", // 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;
    }
    • 第一个参数允许为窗口指定一些可选行为 (例如透明窗口) 。 对于默认行为,此参数设置为零。CreateWindowEx也是多于此处。
    • CLASS_NAME 是窗口类的名称。我们已经定义过了。
    • 标题。 如果窗口具有标题栏,则文本将显示在标题栏中。
    • 窗口样式是一组标志,用于定义窗口的一些外观。 常量WS_OVERLAPPEDWINDOW 实际上是几个标志与按位 OR 组合。 这些标志一起为窗口提供标题栏、边框、系统菜单,以及 最小化 和 最大化 按钮。 此标志集是顶级应用程序窗口最常见的样式。
    • 对于位置和大小,常量CW_USEDEFAULT 表示使用默认值。
    • 下一个参数设置新窗口的父窗口或所有者窗口。 如果要创建子窗口,请设置父窗口。 对于顶级窗口,请将此窗口设置为 NULL
    • 对于应用程序窗口,下一个参数定义窗口的菜单。 此示例不使用菜单,因此该值为 NULL
    • hInstance 是前面所述的实例句柄。
    • 最后一个参数是指向 void 类型任意数据的指针 *。 可以使用此值将数据结构传递给窗口过程。 我们先不管他。

    最后我们要让窗口可见:

    ShowWindow(hwnd, nCmdShow);

    这样窗口就完成创建了。

    窗口消息

    一个程序有交互,我们需要获取这些交互:

    MSG msg;
    GetMessage(&msg, NULL, 0, 0);

    GetMessage可以从消息队列中拉取这些消息,我们并不用担心窗口会无响应。消息队列是另一线程中的。

    GetMessage 的第一个参数是 MSG结构的地址。 如果函数成功,它将用有关消息的信息填充 MSG 结构。 这包括目标窗口和消息代码。 其他三个参数允许筛选从队列获取的消息。 几乎所有情况下,都将这些参数设置为零。

    尽管MSG结构包含有关消息的信息,但几乎永远不会直接检查此结构。 而是将它直接传递给另外两个函数。

    TranslateMessage(&msg); DispatchMessage(&msg);

    TranslateMessage函数与键盘输入相关。

    DispatchMessage 函数告知操作系统调用窗口过程,该窗口是消息的目标。 换句话说,操作系统在其窗口表中查找窗口句柄,查找与窗口关联的函数指针,并调用该函数。

    例如,假设用户按下鼠标左键。 这会导致事件链:

    1. 操作系统在消息队列上放置WM_LBUTTONDOWN消息。
    2. 程序调用GetMessage函数。
    3. GetMessage从队列中拉取 WM_LBUTTONDOWN消息,并填充 MSG结构。
    4. 程序调用 TranslateMessageDispatchMessage函数。
    5. 在 DispatchMessage中,操作系统调用窗口过程。
    6. 窗口过程可以响应消息或忽略它。

    同时GetMessage又接收着退出消息WM_QUIT,GetMessage就会返回0。代码如下:

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

    如果要中途要退出,我们可以用:

    PostQuitMessage(0);

    窗口过程

    DispatchMessage函数会调用消息目标窗口的窗口过程,窗口过程的签名如下:

    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    • hwnd 是窗口的句柄。
    • uMsg 是消息代码;例如, WM_SIZE消息指示窗口已调整大小。
    • wParam 和 lParam 包含与消息相关的其他数据。 确切的含义取决于消息代码。

    然后我们用switch语句去处理WM消息:

    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg)
    {
    //...
    }
    }

    默认消息处理

    如果不在窗口过程中处理特定消息,请将消息参数直接传递给DefWindowProc函数。 此函数对消息执行默认操作,该操作因消息类型而异。

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

    绘制窗口

    image

    这是一个窗口,它由客户区(工作区)非客户区(非工作区)

    • 客户区(Client Area):中间白色的部分

    • 非客户区(Non-client Area):

      • 标题栏

      • 边框

    我们现在只需绘制客户区,下面是将客户区填充白色的一个例子:

    case WM_PAINT:
    {
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);
    FillRect(hdc, &ps.rcPaint,CreateSolidBrush(RGB(255, 255, 255)));
    EndPaint(hwnd, &ps);
    }
    return 0;

    如果要深入研究绘图可以学习 图形设备接口 (GDI)GDI+Direct2D 的内容,其中以上的绘制是 GDI 实现的。

    此示例,我们不需要过多了解。

    关闭窗口

    当用户关闭窗口时,该操作将触发窗口消息序列。窗口会接收到WM_CLOSE

    case WM_CLOSE:
    if (MessageBox(hwnd, L"Really quit?", L"My Application", MB_OKCANCEL) == IDOK)
    {
    DestroyWindow(hwnd);
    }
    return 0;
    case WM_DESTROY:
    PostQuitMessage(0);
    return 0;

    上面的代码我们用MessageBox创建了个包含“确定”和“取消”按钮的对话框。 如果用户单击 “确定”,程序将调用DestroyWindow。 否则,如果用户单击 “取消”,则会跳过对 DestroyWindow的调用,并且窗口保持打开状态。

    当窗口即将被销毁时,它将收到 WM_DESTROY消息。 此消息是在从屏幕中删除窗口后发送的。在主应用程序窗口中,通常通过调用PostQuitMessage来响应WM_DESTROY以退出主循环。

    完整代码

    win32-window.cpp:wWinMain入口点

    win32-window-console.cpp:main入口点

    更多源码请克隆 https://github.com/OrbitGW/blog-sources.git

    Visit me on

    CSDN

    bilibili

    博客园

    知乎

  • 相关阅读:
    【Axure视频教程】取整函数
    利用SpringBoot重写黑马旅游网
    btstack协议栈实战篇--Performance - Stream Data over SPP (Server)
    消息队列 - RabbitMQ
    SpringBoot整合RabbitMQ
    Shunted Self-Attention via Multi-Scale Token Aggregation
    Midjourney风格一致功能解读及使用方法
    【UE5 C++基础 04】UHT基础
    神经网络(九)无监督学习
    Go语言入门心法(十二): GORM映射框架
  • 原文地址:https://www.cnblogs.com/orbitgw/p/16587903.html