WinAPI窗口开发知识

概述

通过以下代码可以创建一个简单的窗口应用程序,我们通过解释以下代码来了解WinAPI编程

#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, 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))
    {
        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);
            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));

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

    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
  1. wWinMain是程序的入口,当程序启动时它会注册一些关于应用窗体行为的一些信息,最重要的方法之一是WindowProc,在案例中定义了行为和界面和如何与用户的交互
  2. 接下来程序创建窗口并且接收标识窗口唯一性的句柄
  3. 如果窗体创建成功,程序进入while循环,直到窗口被关闭并且退出程序

注意的是WindowProc并不会显示的调用,而是通过Windows的一系列消息和DispatchMessage函数来分派每个消息去间接的调用WindowProc,每个消息都会调用一次

Windows消息

windows事件可以发生在任何时刻且顺序基本一致,如何构造一个不能预先预测其执行流程的程序,为此,Windows使用消息传递模型,操作系统传递消息给应用程序,一个消息是一个简单地数字来代表一个特定的消息,当消息传递到窗口,操作系统将调用这个窗口所注册的窗口过程

消息循环

一个应用程序将会接收很多很多的消息,此外一个应用程序也有许多许多窗口,那么我们如何在编写程序时怎么正确的获取消息以及调用给正确的窗口呢,显然应用程序需要循环去检索消息并且派发他们给正确的窗口

在创建窗口的每个线程上,操作系统都为了窗口消息创建了一个队列,在创建这个窗口的线程上这个队列持有这个窗口的所有消息,这个队列是隐藏在你的程序中的,你不能直接操作它,但是你可以通过GetMessage函数从此处拉取数据,例如

MSG msg; // 得到的消息,可能也没有
GetMessage(&msg, NULL, 0, 0);

这个方法移出队列顶端的第一个消息,这个方法将会阻塞,直到有另一个消息被加入队列为止,实际上GetMessage不会让你的程序变的无响应,如果队列中没有消息,那么程序什么都不会做,如果你有执行后台程序,你可以创建在GetMessage等待另一条消息时继续运行的其他线程

GetMessage函数的第一个参数是MSG结构体的地址,如果执行成功,那么将会在MSG内填充关于消息的信息(包括目标窗口和消息码),剩下三个参数可以使你在从队列获取消息时进行过滤,但在通常情况下,都会置空

即使MSG包含着关于消息的信息,但是你通常不会直接使用它,而是将其传给两个函数

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

TranslateMessage方法是与键盘等输入相关的,它转换键盘等输入到字符,你不需要去关系这个是如何工作的,只需要去记得在DispatchMessage之前调用就行

DispatchMessage函数告诉操作系统调用作为消息目标的窗口的窗口过程,换句话说,操作系统在窗口表中查找窗口句柄,找到函数指针相关联的窗口并且那个方法

当窗口过程返回时,返回到DispatchMessage函数,接着到消息循环的下一个消息
,通常情况下GetMessage返回非0值,当你想退出程序并结束循环时可以调用PostQuitMessage(0)方法,这个方法会将WM_QUIT消息放入栈中,WM_QUIT是一个特殊的消息,它会引起GetMessage返回0,从而判断退出循环结束程序

窗口过程

DispatchMessage函数调用消息目标的窗口的窗口过程,而这个窗口过程的签名必须如下

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

关于这四个参数:

  • hwnd:这个窗口的句柄
  • uMsg:是这个消息的消息码
  • wParam、lParam:包含与消息相关的其他数据,这个精确的意义依赖于消息码
  • 返回值:由程序返回给Windows的整形数据,它包含你的程序对特定消息的响应,这个意义依赖于消息码,而CALLBACK是函数的约定而已

默认消息处理器

如果你没有在窗口过程中处理特定的消息,可以通过调用DefWindowProc函数来执行对特定消息的处理

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

绘制窗口

有时你的程序启动绘图来更新窗口的外观,在其他时候操作系统将会通知你让你必须重绘窗口的一部分,当这个窗口重现时,操作系统发送一个WM_PAINT消息,必须绘制的窗口部分称为更新区域

窗体首次显示,这个窗口的全部客户区域必须被绘制,因此,当你显示一个窗口时,你总是会收到至少一条WM_PAINT消息

你只要负责绘制客户区域,其它框架部分有操作系统完成,在你绘制完成之后你将清理更新区域,这将告诉操作系统在某些被改变之前不需要发送其它的WM_PAINT消息

现在支持用户移动窗口,那么有可能窗口的一部分会被隐藏,当隐藏的部分再次显示时,这个被隐藏的部分将会进入更新区域,你的窗口将收到另一个WM_PAINT消息

如果用户拉伸收缩窗口,那么更新区域也会被改变,当用户往右拉伸时,窗口右侧新暴露的区域被添加到更新区域

switch (uMsg)
{
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        // 所有绘制发送在此,位于BeginPaint和EndPaint之间
        FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
        EndPaint(hwnd, &ps);
    }
    return 0;
}

通过调用BeginPaint函数来开始绘制的操作,这个方法将会填充信息和绘制请求到PAINTSTRUCT结构,更新区域由PAINTSTRUCT结构的rcPaint成员给出,此更新区域是相对于客户区域定义的,绘制完成后,调用EndPaint函数。此函数清除更新区域,该区域向窗口发出窗口本身已完成绘制的信号

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值