创建窗口2


***【在线视频教程】***

好文章,来自【福优学苑@音视频+流媒体】

win32窗口应用程序结构

这里介绍的是典型的win32窗口应用程序结构,通常需要以下7个步骤:


  1. 程序入口点(WinMain函数)

  2. 注册窗口类(RegisterClass/EX)

  3. 创建窗口类(CreateWindow/ex)

  4. 显示主窗口(Show Windows)

  5. 更新主窗口(UpdateWindows)

  6. 进入消息循环(GetMessage->TranslateMessage->DispatchMessage->对应的消息处理)

  7. 程序出口点(WinMain返回)

        我们依然使用第一讲创建的Windows桌面应用程序进行说明。其中一些代码我们跳过,因为不(wo)重(bu)要(hui)。


注册窗口类

        程序入口点我们上一讲已经讲过,接下来看注册窗口类。任何窗口创建前需要先注册窗口。对应程序中的代码如下:


//  函数: MyRegisterClass()

//

//  目标: 注册窗口类。

//

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;//窗口类结构体,声明一个变量,其中包含了窗口的各种属性
    wcex.cbSize = sizeof(WNDCLASSEX);//结构体的大小
    wcex.style          = CS_HREDRAW | CS_VREDRAW;//窗口的风格
    wcex.lpfnWndProc    = WndProc;//指定窗口函数为WndProc
    wcex.cbClsExtra     = 0;//窗口类占用的额外内存,可以存放窗口类的共有数据,默认为0,不重要
    wcex.cbWndExtra     = 0;//窗口占用的额外内存,可以选择其中存放的每个窗口所拥有的数据,默认为0,不重要
    wcex.hInstance      = hInstance;//应用程序实例句柄,已经由WinMain函数传递过来了
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));//图标句柄,这里通过LoadIcon获得系统默认的图标
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);//鼠标的光标句柄,依然使用系统默认的光标
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);//背景画刷句柄,即指窗口背景颜色
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);//菜单的资源名称
    wcex.lpszClassName  = szWindowClass;//窗口类的名字,我们创建窗口时是依靠名字来制定窗口类的
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));//小图标的句柄,指当窗口最小化时,任务栏显示的程序图标
    return RegisterClassExW(&wcex);//传递给RegisterClassExW以注册窗口
}

     上面我们使用了结构体WNDCLASSEXW(或去掉W)。当然也可以使用WNDCLASS结构体,它和WNDCLASSEX的区别仅仅是EX是增强型窗口,比它多两个参数而已。多的参数一个是指定数据结构的cbsize,一个是支持“小图标”的hIconsm。以后我们接触的很多类和函数也常有EX结尾和无EX结尾之分,区别也都是增强型和非增强型而已。

        cbSize代表wcex结构体变量的大小,我们就是通过这个参数区别wcex是增强版还是非增强版。


窗口类风格

        style是类的风格,CS_HREDRAW | CS_VREDRAW意味着当窗口宽度或高度发生变化时,窗口将根据窗口大小重新绘制。其中前缀CS表示class type,HREDRAW代表当宽度(H代表水平方向Horizontal)发生改变时进行重绘(Redraw),VREDRAW代表着当高度(V代表垂直方向Vertical)方向发生变化时进行重绘。按位或|则意味着两个同时有效。为什么会这样呢?在WinUser.h中定义了全部的可选样式,如下:

#define CS_VREDRAW0x0001
#define CS_HREDRAW0x0002
#define CS_DBLCLKS0x0008
#define CS_OWNDC0x0020
#define CS_CLASSDC0x0040
#define CS_PARENTDC0x0080
#define CS_NOCLOSE0x0200
#define CS_SAVEBITS0x0800
#define CS_BYTEALIGNCLIENT0x1000
#define CS_BYTEALIGNWINDOW0x2000
#define CS_GLOBALCLASS0x4000


 在这里,预定义的符号常量实际上使用了不同的数据位,所以在进行或运算不会发生混淆。


创建窗口类

        注册完毕后,调用CreateWindow(或CreateWindowW、CreateWindowEX)来创建窗口,函数原型如下:


HWND WINAPI CreateWindow(
  _In_opt_ LPCTSTR   lpClassName,//创建的窗口的基础类名
  _In_opt_ LPCTSTR   lpWindowName,//窗口的名称
  _In_     DWORD     dwStyle,//窗口的风格
  _In_     int       x,//该窗口左上角位置x坐标
  _In_     int       y,//该窗口左上角位置y坐标
  _In_     int       nWidth,//窗口的宽度
  _In_     int       nHeight,//窗口的高度
  _In_opt_ HWND      hWndParent,//父窗口的句柄
  _In_opt_ HMENU     hMenu,//菜单句柄
  _In_opt_ HINSTANCE hInstance,//应用程序句柄
  _In_opt_ LPVOID    lpParam//通过WM_CREATE消息参数传递给窗口函数
);

创建窗口对应于我们程序中的这两行(在InitInstance函数里面):


 HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,

      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);


        其中,CW_USEDEFAULT是指默认窗口尺寸。CreateWindowW的返回值:如果函数成功, 则返回值是新窗口的句柄;如果函数失败, 返回值为NULL。


窗口风格

        第三个参数,窗口的风格(或称窗口的类型),供用户指定窗口的绘制及其行为。其中有3种最重要的风格创建了3种最基本的窗口类型:重叠窗口、弹出窗口、子窗口。


    重叠窗口(Overlapped Window):具有应用程序主窗口的全部特点。它的非客户区包括一个可伸缩的框架、菜单栏、标题栏和最大化、最小化、关闭按钮。

弹出窗口(Popup Window):具有消息框或对话框的全部特点。它的非客户区包括一个固定大小的框架和一个标题栏。

子窗口(Child Window):具有类似按钮控件的全部特点。没有非客户区,窗口的处理过程负责绘制窗口的每个部分。

        如果我们想创建一个在游戏中常用的全屏窗口,应该如下调用CreateWindow:

HWND hWnd=CreateWindow(
    szWindowClass,
    szTitle,
    WS_POPUP,//固定大小,选择弹出窗口
    0,0,//窗口的位置在屏幕的左上角
    GetSystemMetrics(SM_CXSCREEN),//与屏幕等宽
    GetSystemMetrics(SM_CYSCREEN),//与屏幕等高
    nullptr,
    nullptr,
    hInstance,
    nullptr
    );


        可以用上述代码取代原程序中的代码,运行以下,看一下效果。

        其中,GetSystemMetrics函数获得了屏幕的宽度和高度。除此之外,该函数还可以获得其他系统属性,包括菜单栏的高度、工具栏的高度、窗口边框的高度等等,不一一描述了。

原程序的窗口风格使用的是WS_OVERLAPPEDWINDOW,我们可以查看一下它的定义(按住ctrl,鼠标悬在它上面时会变成一个小手,就可以点击查看了)。定义如下:

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | \
                             WS_CAPTION        | \
                             WS_SYSMENU        | \
                             WS_THICKFRAME     | \
                             WS_MINIMIZEBOX    | \
                             WS_MAXIMIZEBOX)


    (上述代码中的\表示换行输入)然后我们就知道了,这种风格其实上包含了WS_OVERLAPPED(可重叠)、WS_CAPTION(有标题栏)、WS_SYSMENU (有系统菜单)、WS_THICKFRAME(粗边框)、WS_MINIMIZEBOX(最小化按钮)、WS_MAXIMIZEBOX(最大化按钮)。

        如果现在我们想在该风格的基础上把最大化按钮去掉,其他保留,应该怎么做呢?根据与或运算,应该这样写:WS_OVERLAPPEDWINDOW &~ WS_MAXIMZEBOX (不理解的话可以举几个数运算一下)


显示窗口

        ShowWindow(hWnd, nCmdShow);这句话的功能是把我们创建好的窗口显示出来。

        窗口句柄hWnd作为实参传递,告诉系统要显示哪个窗口。nCmdShow则是说明窗口显示的方式,是系统传递给WinMain的参数。nCmdShow可以取不同的值,例如SW_HIDE将隐藏创建的窗口,SW_MINIMIZE将最小化创建窗口,等等。


更新窗口

        UpdateWindow(hWnd);这句话用来更新窗口。


消息循环

        当显示完窗口,应用程序已经准备好从用户接收键盘和鼠标的输入了,必须加入消息循环,不断对各种消息进行响应,否则程序会陷入无响应的状态。程序中消息循环的语句是:


MSG msg;
    // 主消息循环:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

        MSG是一个结构体,其原型如下:

typedef struct tagMSG {
  HWND   hwnd;//窗口句柄,nullptr意味着当前窗口
  UINT   message;//消息类型
  WPARAM wParam;//消息参数
  LPARAM lParam;//消息参数
  DWORD  time;//获取消息的时间
  POINT  pt;//获取消息时光标的位置
} MSG, *PMSG, *LPMSG;

        我们对它的原型不需要过多了解,只知道利用GetMessage函数可以中从消息队列取出一个消息放在MSG类型中。GetMessage的原型如下:

BOOL WINAPI GetMessage(
  _Out_    LPMSG lpMsg,//结构体变量的指针
  _In_opt_ HWND  hWnd,//窗口句柄,nullptr意味着当前窗口
  _In_     UINT  wMsgFilterMin,//这两个参数用于消息过滤,通常为0
  _In_     UINT  wMsgFilterMax
);

        如果消息队列中没有任何消息,GetMessage 会一直等下去,直到有消息进入消息队列。如果GetMessage从消息队列中取得的消息是WM_QUIT(退出),则返回0;否则返回非零值。我们利用返回值0来结束消息循环。而消息循环的结束,最终导致WinMain函数的结束,意味着程序结束。

        TranslateMessage(&msg)用于把键盘输入转换成字符消息,例如把WM_KEYDOWN和WM_KEYUP转换成WM_CHAR。

        DispatchMessage(&msg)用于把消息分发到对应窗口的窗口函数中。


窗口函数

        上一讲我们提到了窗口函数,下面详细讲解。我们把函数原型再搬过来:

LRESULT CALLBACK WndProc(
    HWND hWnd,//窗口句柄
    UINT uMsg,//消息类型
    WPARAM wParam,//消息参数
    LPARAM lParam //消息参数)


        窗口函数接收到的所有消息都被标志为一个符号常量,这就是该函数第二个参数uMsg。这些符号常量在WinUser.h中定义,常用的Windows消息可以参考下面的博客:

https://blog.csdn.net/zhangguofu2/article/details/19236081

        下面我们看看WindowProc函数里面都有什么东西:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND://处理WM_COMMAND消息
        {
            int wmId = LOWORD(wParam);
            // 分析菜单选择:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT://处理WM_PAINT消息
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY://处理WM_DESTROY消息
        PostQuitMessage(0);
        break;
    default://对于不需要处理的消息,则调用默认的信息处理函数
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

应用程序的退出

        当用户关闭窗口时,窗口函数会收到一个WM_DESTROY消息。正如上面的代码所示,窗口函数应该调用PostQuitMessage(0)向消息队列中插入一个WM_QUIT消息。

        一定要注意,WM_DESTROY是窗口函数必须要处理的消息,DefWindowProc并没有我们所需要的功能。如果把PostQuitMessage(0);这句话注释掉,运行程序,然后关闭窗口,你会发现窗口已然消失了,似乎一切正常。但你会发现调试菜单表明程序依然在运行;打开任务管理器,也会发现程序在运行。这能说明两个问题:


DefWindowProc没有实现PostQuitMessage(0)的功能。

在Windows中,窗口和进程是有联系的两个对象。窗口关闭了,不能说明进程也关闭了。

其实,从单击窗口右上角的“关闭”按钮,到程序退出,是一个复杂的过程,描述如下:


单击关闭按钮,系统向消息队列中插入WM_CLOSE消息

窗口函数调用DefWindowsProc处理WM_CLOSE消息:调用DestroyWindow函数

窗口关闭,并向消息队列中插入WM_DESTROY消息

窗口函数处理WM_DESTROY消息:调用PostQuitMessage函数,向消息队列中插入WM_QUIT消息

主函数的消息循环中的GetMessage获取WM_QUIT消息,返回0,导致消息循环结束,进而WinMain函数结束,再进而整个进程结束。

        现在思考一下,能不能在结束应用程序之前弹出一个对话框来确认我们的操作呢(有误操作的可能)?我们看到,窗口函数中没有对WM_CLOSE消息进行处理,而是调用DefWindowProc进行默认处理。所以,要想在退出前弹出对话框,就要用自己的代码处理WM_CLOSE消息。修改如下:

switch(msg)
{
case WM_CLOSE:
    nsel=MessageBox(hWnd,
                    L"你真的要退出吗?",
                    szWindowTitle,
                    MB_YESNO|MB_ICONQUESTION);
    if(nsel==IDYES)DestroyWindow(hwnd);
    return 0;
case WM_DESTROY:
    POSTQUITMESSAGE(0);
    RETURN 0;
default:
    return DefWindowProc(hWnd,msg,wParam,lParam);


到此为止,我们将程序中的代码讲解得差不多了






好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***