我的第一个Windows窗口应用程序

第一个Windows窗口应用程序

  • 创建一个窗口应用程序有4个步骤:

    1. 注册窗口类
    2. 创建窗口
    3. 显示窗口
    4. 更新窗口
  • 窗口类 WNDCLASS

    • 窗口类不是C++中的类,可以理解为“类别”

    • 窗口类是用来创建窗口的模板

    • 每一个窗口类都有一个窗口过程(WndProc),负责处理发送该类窗口的所有消息

    • 在创建某个类型的窗口前,必须先注册该窗口类

    • 窗口类有3种

      • 系统窗口类
        • 系统内部使用的
      • 应用程序全局窗口类
        • 注册窗口时以 CS_GLOBALCLASS 标志注册的窗口类(就是当 style 属性中有 CS_GLOBALCLASS 时)
        • 这个窗口类在这个进程中,所有模块都可以使用(模块一般是动态库模块),一些程序的皮肤库很多都是以这种形式实现的
      • 应用程序局部窗口类
        • 注册窗口时没有以 CS_GLOBALCLASS 标志注册的窗口类(就是当 style 属性中没有 CS_GLOBALCLASS 时)
        • 这个窗口类只能在模块范围内使用,对其他模块不可见
    •   // WNDCLASS 窗口类结构体
        typedef struct tagWNDCLASSA {
            UINT style;//窗口类的风格
            WNDPROC lpfnWndProc;//窗口过程函数,负责处理发送该窗口的所有信息
            int cbClsExtra;//指定窗口类结构之后要分配的额外字节数。系统将字节初始化为零
            int cbWndExtra;//指定窗口实例之后要分配的额外字节数。系统将字节初始化为零
            HINSTANCE hInstance;//窗口的实例句柄
            HICON hIcon;//该窗口类所用的图标
            HCURSOR hCursor;//该窗口类所用的光标
            HBRUSH hbrBackground;//该窗口类所用的背景刷
            LPCSTR lpszMenuName;//该窗口类所用的菜单资源名称
            LPCSTR lpszClassName;//该窗口类名称
        } WNDCLASSA;
      
    • 窗口类的 style 属性可选择的值。如果要同时使用多个,可以用 | 连接

      • CS_HREDRAW 当水平长度改变或移动窗口时,重画整个窗口
      • CS_VREDRAW 当垂直长度改变或移动窗口时,重画整个窗口
      • CS_NOCLOSE 禁止系统菜单的关闭选项
      • CS_DBLCLKS 允许向窗口发送双击鼠标键的信息
      • CS_DROPSHADOW 开启边框阴影
  • 使用 RegisterClass 函数注册窗口类

    • 他的参数只有一个,就是一个 WNDCLASS 结构体的指针
    • 如果函数成功,返回值是一个 ATOM 类型(本质上就是unsigned short类型),是一个唯一标识
    • 如果函数失败,返回0,可以调用 GetLastError 函数获取失败的原因
    •   ATOM WINAPI RegisterClass(
            const WNDCLASS *lpWndClass  //指向WNDCLASS结构的长指针
        );
      
  • 使用 CreateWindow 函数创建窗口

    •   HWND WINAPI CreateWindow (
            LPCTSTR lpClassName,  //RegisterClass 注册的窗口类名称
            LPCTSTR lpWindowName,  //窗口名称
            DWORD dwStyle,  //窗口的样式
            int x,  //初始x坐标(左上角的起始点)
            int y,  //初始y坐标(左上角的起始点)
            int nWidth,  //窗口的宽度, CW_USEDEFAULT 表示默认宽度和高度
            int nHeight,  //窗口的宽度, CW_USEDEFAULT 表示默认宽度和高度
            HWND hWndParent,  //父窗口句柄
            HMENU hMenu,  //窗口菜单的句柄(如果窗口有菜单栏)
            HINSTANCE hInstance,  //模块实例的句柄
            LPVOID lpParam  //通过 WM_CREATE 消息的 IParam 参数指向的 CREATESTRUCT 结构(还没学到,暂时传NULL)
        )
      
    • 第一个参数表示窗口类的名称,跟 WNDCLASS 的最后一个参数一致
    • 第二个参数是窗口的标题
    • 第三个参数 dwStyle 是窗口的样式,可取值有
      • WS_BORDER 创建具有边框的窗口
      • WS_CAPTION 创建有标题栏的窗口
      • WS_CHILD 创建一个子窗口
      • WS_VISIBLE 创建初始可见的窗口
      • WS_MAXIMIZEBOX 创建一个窗口,具有一个最大化按钮
      • WS_MINIMIZEBOX 创建一个窗口,具有一个最小化按钮
      • WS_OVERLAPPEDWINDOW 创建一个具有标题栏、最大化、最小化、关闭按钮、可调整大小的窗口。WS_TILEDWINDOW 是 WS_OVERLAPPEDWINDOW 的别名
    • 最后一个参数还没学到,暂时传NULL
    • 如果函数成功了,返回值是一个 HWND 类型,是一个窗口句柄。如果失败则返回NULL,可以通过 GetLastError 函数获取更多失败信息
    • 在函数返回之前,CreateWindow 会发送 WM_CREATE 消息给窗口过程
  • 使用 ShowWindow 函数显示窗口

    •   BOOL WINAPI ShowWindow (
            HWND hWnd,  //窗口的句柄
            int nCmdShow  //控制如何显示窗口
        )
      
    • 第一个参数是窗口的句柄
    • 第二个参数表示如何显示窗口,是int类型
      • 第一次调用 ShowWindow 时,应该使用 WinMain 函数的 nCmdShow 参数值作为其参数值
      • 随后对 ShowWindow 的调用必须使用给定列表中的一个值,而不是 WinMain 函数的 nCmdShow 参数值(而事实上可以用WinMain函数的nCmdShow,不是硬性要求)
    • 如果窗口先前可见,则返回值不为0。如果窗口先前被隐藏,则返回值为0
  • 使用 UpdateWindow 函数更新窗口

    •   BOOL WINAPI UpdateWindow (
            HWND hWnd  //窗口的句柄
        )
      
    • 只有一个参数,是窗口句柄
    • 如果函数成功,则返回值为非0,如果失败则返回值为0
    • UpdateWindow 通过发送 WM_PAINT 到指定窗口的窗口过程来更新(绕过消息队列)。如果更新区域为空则不发送消息
  • 窗口过程 WNDPROC

    • windows应用程序采用的是消息机制:用户的任何操作呢都会发送相应的消息,然后相应的消息呢丢给窗口过程函数来进行处理
    • WNDPROC 是一个函数指针,指向窗口过程函数
    • 第一个参数为窗口句柄
    • 第二个参数为消息ID, UINT 类型(typedef unsigned int UINT;)
    • 第三个参数为附加消息类型,取决于 uMsg 参数的值, WPARAM 类型
    • 第四个参数为附加消息信息,取决于 uMsg 参数的值, LPARAM 类型
    • 返回值为 LRESULT 类型(typedef long LRESULT;)
    • 默认窗口过程函数
      • 在Windows操作系统里,当窗口显示之后,用户在窗口上操作(比如移动鼠标、单击窗口、关闭窗口)时,系统会向该窗口源源不断地发送消息,然后窗口就需要处理这些消息,因此就需要一个函数来处理这些消息。Win32API里定义了一个系统默认的窗口处理函数 DefWindowProc ,我们可以把不关心的消息都丢给他来处理,如果我们需要自定义处理相关的消息,则需要实现自己的窗口过程函数
      • 返回值是消息处理的结果
  • 消息循环(消息队列)

    • Windows 中有一个"系统消息队列",以及分别为每个GUI线程维护一个各自的"线程消息队列"(应用程序消息队列)

      • GUI线程是指线程中调用了user32.dll或者gdi32.dll中的函数,就叫它GUI线程
      • 只有在调用了user32.dll或者gdi32.dll中的函数后,系统才会自动为线程创建消息队列。每个线程只能有一个消息队列
    • 一个点击鼠标的消息从产生到被窗口过程处理,有以下步骤

      1. 鼠标驱动程序根据用户事件,转换成消息并自动放在Windows的"系统消息队列"
      2. Windows系统会自动将"系统消息队列"中的消息取出,并投掷于消息对应的"线程消息队列"
      3. 需要你从"线程消息队列"中取消息,然后翻译消息、分发消息给窗口过程
      4. 窗口过程函数响应这个消息并进行处理(窗口过程函数可以自定义,也可以使用默认的)
    • 所以需要在你的程序中写一个while循环,用 GetMessage 函数来一直从"线程消息队列"中取消息

      • 如果返回值为0,说明接收到了 WM_QUIT 消息(代表收到了结束信号,所以应该跳出while循环)
      • 如果返回值不为0,则说明检索到的是 WM_QUIT 以外的消息
      • 如果有错误,返回值是-1,可以调用 GetLastError 函数获取失败的原因
    • 接收到了消息之后还要用 TranslateMessage 函数来翻译消息,将虚拟键的消息转为字符消息

      • 如果消息已被翻译,则返回值是非零值
      • 如果消息是 WM_KEYDOWN,WM_KEYUP,WM_SYSKEYDOWN,WM_SYSKEYUP,不管翻译如何,返回值都是非零值
      • 如果消息未被转换,则返回值为零
    • 翻译完之后还要用 DispatchMessage 函数把消息发送给窗口过程函数来处理

    •   BOOL WINAPI GetMessage(
            LPMSG lpMsg,  //指向MSG结构的指针
            HWND hWnd, //要检索的窗口句柄
            UNIT wMsgFilterMin,  //要检索的最低消息值的整数值,一般传0
            UINT wMsgFilterMax  //要检索的最高消息值的整数值,一般传0
        )
      
        //消息循环(消息队列)
        MSG msg;
        while(GetMessage(&msg, NULL, 0, 0)){
            TranslateMessage(&msg);  //消息的翻译
            DispatchMessage(&msg);  //消息的分发
        }
      
  • SendMessage 函数可以直接发送消息到窗口过程,绕过线程消息队列

    •   BOOL WINAPI SendMessage(
            HWND hWnd, //接收消息的窗口句柄
            UINT Msg,  //消息ID
            WPARAM wParam,  //附加的消息特定的信息
            LPARAM lParam  //附加的消息特定的信息
        )
      
    • SendMessage 将指定的消息发送到一个或多个窗口,直到窗口过程处理该消息前会一直阻塞等待
    • SendMessage 函数可以直接发送消息到窗口过程,绕过线程消息队列
#include<windows.h>
#include <iostream>

// 如果你想自定义窗口过程函数,四个参数必须严格一致,返回值也必须为LRESULT,调用约定也必须是 CALLBACK( __stdcall 的别名)
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_CREATE:
            //这个是由 CreateWindow 发出来的
            return 0;
        case WM_PAINT:
            //这个是由 UpdateWindow 发出来的
            return 0;
        case WM_DESTROY:
            //这个是由 DestroyWindow 发出来的(点击右上角关闭按钮)
            PostQuitMessage(0);  //发送 WM_QUIT 消息来退出程序
            return 0;
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    //第一步:注册窗口类
    WNDCLASS wnd;
    wnd.style = CS_DROPSHADOW;  //样式
    wnd.lpfnWndProc = MyWndProc;  //窗口过程函数,用于处理消息
    wnd.cbClsExtra = 0;
    wnd.cbWndExtra = 0;
    wnd.hInstance = hInstance;
    wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION);  //icon图标
    wnd.hCursor = LoadCursor(NULL, IDC_ARROW);  //鼠标光标采用箭头光标
    wnd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
    wnd.lpszClassName = L"MrHuang";  //窗口类名
    wnd.lpszMenuName = NULL;  //菜单名称
    RegisterClass(&wnd);

    //第二步:创建窗口(WM_CREATE)
    HWND hWnd = CreateWindow(L"MrHuang", L"my first windows app", WS_OVERLAPPED, 100, 100, 300, 300, NULL, NULL, hInstance, NULL);

    //第三步:显示窗口
    ShowWindow(hWnd, nShowCmd);

    //第四步:更新窗口(WM_PAINT)
    UpdateWindow(hWnd);

    //反复从线程消息队列中取消息,然后转发给窗口过程
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);  //翻译消息,将虚拟键消息转换为字符消息
        DispatchMessage(&msg);  //把消息分发给窗口过程函数
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值