Windows窗口开发原理(窗口的创建&消息机制)

在windows应用程序中,窗口是其非常重要的一个元素。并且窗口是通过窗口句柄来标识的。句柄(HANDLE)是windows程序中一个重要的概念,其标识各种资源,包括图标句柄(HICON)、光标句柄(HCURSOR)和画刷句柄(HBRUSH)。

下面以一个带有自定义的画刷、光标和图标的windows窗口为例,讲解win32窗口的创建过程。

windows消息机制

windows程序是基于事件驱动方式的程序设计模式,主要是基于消息的。比如当用户在窗口中画图的时候,按下鼠标左键,此时os会感知到这一事件,于是将此事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息,经过transltor翻译、分发消息,然后交由os调用

窗口过程函数(应用程序注册的回调函数)或(DefWindowProc系统默认的回调处理函数)进行处理。

创建一个窗口的基本流程

设计窗口类

窗口的特征是由WNDCLASS结构体来定义的,其定义了这个窗口的基本属性,所以我们只需填充结构体各成员信息即可。

typedef struct tagWNDCLASSW {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR     lpszMenuName;
    LPCWSTR     lpszClassName;
} WNDCLASSW
typedef WNDCLASSW WNDCLASS;//WNDCLASS是WNDCLASSW的别名
View Code

这里要首先说明下各类型定义

//LRESULT :long
// UINT:unsigned int
//WPARAM:unsigned int
//LPARAM:unsigned int
//LPCWSTR:const w_chart_t *;
// typedef WORD                ATOM;   //BUGBUG - might want to remove this from minwin
// typedef unsigned short      WORD;
// typedef unsigned long       DWORD;
//LPCWSTR const w_char_t *   宽字符
//LPCSTR  const char *
    wchar_t szAppclassName[] = _T("FirstWin32");
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口类的样式(要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用用位或|操作符将其组合起来)
    //WNDPROC 函数指针类型
    wc.lpfnWndProc = WindowProc;//窗口回调函数/窗口处理函数
    wc.cbClsExtra = 0;//窗口类的附加内存大小
    wc.cbWndExtra = 0;//窗口附加内存大小
    wc.hInstance = hInstance;//当前应用程序实例句柄
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加载自定义的图标句柄
    //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光标句柄;加载系统光标,也可以采用下面的方式加载自定义的光标
    wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
    wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//红绿蓝三原色0~255,[0最暗,255最亮]
    wc.lpszMenuName = NULL;//菜单名
    wc.lpszClassName = szAppclassName;//窗口类型名 spy++(vs->工具选项)

其中的第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数时一个回调函数。该函数签名如下:

 WNDPROC     lpfnWndProc;
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

故窗口函数按照如下签名定义,实现如下:

//窗口处理函数
//第一个参数:当前窗口句柄
//第二个参数:消息类型
//第三个参数:附加消息、附加操作
//第四个参数:附加消息、附加操作
LRESULT CALLBACK  WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE://窗口关闭消息
        DestroyWindow(hWnd);//销毁窗口,干掉界面,不会发出WM_QUIT,会发出一个WM_DESTORY消息
        break;
    case WM_QUIT://窗口销毁消息
        PostQuitMessage(0);//发布WM_QUIT
        break;
    default:
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

回调函数机制:

⑴定义一个回调函数(WindowProc);
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者(注册wc.lpfnWndProc = WindowProc);
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理(eg双击鼠标按钮时间发生后,os调用窗口过程函数进行处理)。

注册窗口类

 通过ATOM WINAPI RegisterClass( _In_ CONST WNDCLASS*lpWndClass);进行窗口类注册。形参为WNDCLASS地址。返回值为ATOM.

创建窗口

 创建窗口通过CreateWindow函数来实现,该函数API信息如下:

void CreateWindow(
   lpClassName,
   lpWindowName,
   dwStyle,
   x,
   y,
   nWidth,
   nHeight,
   hWndParent,
   hMenu,
   hInstance,
   lpParam
);
View Code
关于返回值:
类型:HWND 如果函数成功,则返回值是新窗口的句柄。 如果函数失败,则返回值为NULL。要获取扩展错误信息,请调用GetLastError。
//WS:window style
    HWND hWnd = CreateWindow(szAppclassName,                        //窗口类型名
        _T("这个是我的第一个windows应用程序")               //窗口左上角标题
        , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的风格
        200, 300,                                       //窗口左上角坐标
        800, 600,                                        // //窗口宽和高
        NULL,                                            //父窗口句柄
        NULL,                                            //菜单句柄
        hInstance,                                        //应用程序实例句柄
        NULL);                                        //创建窗口的附加参数,WM_CREATE消息,lparam来接受这个参数
    if (NULL == hWnd)
    {
        MessageBox(NULL, _T("创建窗口失败"), _T("提示"), MB_OK);
    }
View Code

显示窗口&更新窗口

 创建完窗口后,要对窗口进行具体的显示。

显示函数API如下:

BOOL ShowWindow(
  HWND hWnd,//创建窗口后返回的窗口句柄
  int  nCmdShow //指定窗口显示的状态,包括SW_MAXIMIZE  SW_MINIMIZE SW_NORMAL SW_HIDE  最大化、最小化、正常、隐藏显示
);

在调用ShowWindow函数之后,需要调用UpdateWindow来刷新窗口。

UpdateWindow(hWnd);//hWnd是创建成功后的窗口句柄

注意:UpdateWindow函数通过发送一个WM_PAINT消息来刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到消息队列中。

消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,调用GetMessage函数不断从消息队列中取出消息,并进行相应。

消息结构体:

//        typedef struct tagMSG {
    //HWND        hwnd;      //消息发向窗口的窗口句柄(指的是这个消息发个哪个窗口了,这里指定这个窗口的句柄)
    //UINT        message;   //消息编号
    //WPARAM      wParam;  //附加消息
    //LPARAM      lParam;//附加消息
    //DWORD       time;//消息放入消息队列的时间
    //POINT       pt;//消息放入消息队列时鼠标坐标
View Code
BOOL GetMessage(
  LPMSG lpMsg,//消息结构体地址
  HWND  hWnd,//窗口句柄
  UINT  wMsgFilterMin,//要获取的消息的最小值,通常设置为0
  UINT  wMsgFilterMax//要获取的消息的最大值。如果FiterMin,Max两者都为0,则接收所有消息
);

代码如下:

MSG msg;
    //GetMessage:何时返回FALSE
    //当获取到WM_QUIT消息时,返回FALSE,没有获取到这个消息时,返回非0,不会退出循环
    while (GetMessage(&msg, NULL, 0, 0))
    {
        //将虚拟键消息转换为字符消息
        TranslateMessage(&msg);
        //将消息分发给窗口处理函数
        DispatchMessage(&msg);
    }

此处的Windows应用消息的消息处理机制如下图:

 

 

 

(1)os接收到应用消息的窗口消息【比如当用户在窗口中画图的时候,按下鼠标左键,此时os会感知到这一事件,于是将此事件包装成一个消息】,将消息投递到该应用消息的消息队列中。

(2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,比如放弃对某些消息的相应或调用TranslateMessage产生新的消息。

(3)应用程序调用DispatchMessage将消息回传给os。消息MSG结构体中包含接受消息的窗口的句柄。因此DispatchMessage函数总能进行正确传递。

(4)os利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。

 附录完整代码

#include<Windows.h>
#include<tchar.h>
#include"resource.h"
//LRESULT :long
// UINT:unsigned int
//WPARAM:unsigned int
//LPARAM:unsigned int
//LPCWSTR:const w_chart_t *;
// typedef WORD                ATOM;   //BUGBUG - might want to remove this from minwin
// typedef unsigned short      WORD;
// typedef unsigned long       DWORD;
//LPCWSTR const w_char_t *   宽字符
//LPCSTR  const char *
//窗口处理函数
//第一个参数:当前窗口句柄
//第二个参数:消息类型
//第三个参数:附加消息、附加操作
//第四个参数:附加消息、附加操作
LRESULT CALLBACK  WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE://窗口关闭消息
        DestroyWindow(hWnd);//销毁窗口,干掉界面,不会发出WM_QUIT,会发出一个WM_DESTORY消息
        break;
    case WM_QUIT://窗口销毁消息
        PostQuitMessage(0);//发布WM_QUIT
        break;
    default:
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
    //创建一个窗口的流程
    //1设计窗口类
  /*  typedef struct tagWNDCLASSW {
        UINT        style;
        WNDPROC     lpfnWndProc;
        int         cbClsExtra;
        int         cbWndExtra;
        HINSTANCE   hInstance;
        HICON       hIcon;
        HCURSOR     hCursor;
        HBRUSH      hbrBackground;
        LPCWSTR     lpszMenuName;
        LPCWSTR     lpszClassName;
    } WNDCLASSW,*/
    //typedef WNDCLASSW WNDCLASS;

    //LRESULT CALLBACK WindowProc(
    //    _In_ HWND   hwnd,
    //    _In_ UINT   uMsg,
    //    _In_ WPARAM wParam,
    //    _In_ LPARAM lParam
    //);
    //typedef LRESULT(CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
    wchar_t szAppclassName[] = _T("FirstWin32");
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口类的风格
    //WNDPROC 函数指针类型
    wc.lpfnWndProc = WindowProc;//窗口回调函数/窗口处理函数
    wc.cbClsExtra = 0;//窗口类的附加内存大小
    wc.cbWndExtra = 0;//窗口附加内存大小
    wc.hInstance = hInstance;//当前应用程序实例句柄
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加载自定义的图标句柄
    //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光标句柄;加载系统光标,也可以采用下面的方式加载自定义的光标
    wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
    wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//红绿蓝三原色0~255,[0最暗,255最亮]
    wc.lpszMenuName = NULL;//菜单名
    wc.lpszClassName = szAppclassName;//窗口类型名 spy++(vs->工具选项)


    //2注册窗口类
    //返回值ATOM
    if (0 == RegisterClass(&wc))
    {
        MessageBox(NULL, _T("此程序不能复制在winNT平台"), _T("提示"), MB_OK);
        return 0;
    }
    //3创建窗口
    //WS:window style
    HWND hWnd = CreateWindow(szAppclassName,                        //窗口类型名
        _T("这个是我的第一个windows应用程序")               //窗口左上角标题
        , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的风格
        200, 300,                                       //窗口左上角坐标
        800, 600,                                        // //窗口宽和高
        NULL,                                            //父窗口句柄
        NULL,                                            //菜单句柄
        hInstance,                                        //应用程序实例句柄
        NULL);                                        //创建窗口的附加参数,WM_CREATE消息,lparam来接受这个参数
    if (NULL == hWnd)
    {
        MessageBox(NULL, _T("创建窗口失败"), _T("提示"), MB_OK);
    }
    //4显示窗口
    //SW_SHOW:原来在何处显示,就在此处显示
    //SW_MAXIMIZE  SW_MINIMIZE     SW_NORMAL SW_HIDE  最大化、最小化、正常、隐藏显示




    ShowWindow(hWnd, SW_SHOW);
    //5更新窗口
    UpdateWindow(hWnd);
    //6消息循环
    //        typedef struct tagMSG {
    //HWND        hwnd;      //消息发向窗口的窗口句柄(指的是这个消息发个哪个窗口了,这里指定这个窗口的句柄)
    //UINT        message;   //消息编号
    //WPARAM      wParam;  //附加消息
    //LPARAM      lParam;//附加消息
    //DWORD       time;//消息放入消息队列的时间
    //POINT       pt;//消息放入消息队列时鼠标坐标
    //windows程序都是通过消息机制驱动运行的。
    MSG msg;
    //GetMessage:何时返回FALSE
    //当获取到WM_QUIT消息时,返回FALSE,没有获取到这个消息时,返回非0,不会退出循环
    while (GetMessage(&msg, NULL, 0, 0))
    {
        //将虚拟键消息转换为字符消息
        TranslateMessage(&msg);
        //将消息分发给窗口处理函数
        DispatchMessage(&msg);
    }


    MessageBox(NULL, _T("这是第一个win32应用程序"), _T("提示"), MB_OK);
    return 0;
}
View Code
posted @ 2021-10-03 21:19  liweiyin  阅读(1620)  评论(0编辑  收藏  举报