剖析MFC消息机制

MFC消息机制


作者:Mrzhu007
日期:2018-06-02
博客地址:金色世界


说明

为了以后方便了解。将目前了解的MFC消息映射记录下来方便以后查看

传统的SDK编程

Windows操作系统是基于消息的事件驱动模式。消息是win32应用程序运行的源泉。下面为传统的SDK编程中,消息处理。

include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); //窗口消息处理使用的回调函数

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT ("HelloWin") ;
    HWND hwnd ;
    MSG  msg ;
    WNDCLAS  wndclass ;
    wndclass.style  = CS_HREDRAW | CS_VREDRAW ; // 视窗类别样式
    wndclass.lpfnWndProc = WndProc ; // 消息处理的回调函数
    wndclass.cbClsExtra  = 0 ;  // 预留空间
    wndclass.cbWndExtra  = 0 ;  // 预留空间
    wndclass.hInstance  = hInstance ; // 主函数参数,有操作系统分配的句柄。指当前进程
    wndclass.hIcon  = LoadIcon (NULL, IDI_APPLICATION) ; // 应用程序图标
    wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;  // 鼠标
    wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; // 指定窗口背景画刷。这里使用白色画刷
    wndclass.lpszMenuNam  = NULL ; // 这里指定窗体的功能菜单。 
    wndclass.lpszClassName = szAppName ; // 窗体类名 这是即指“HelloWin”
    if (!RegisterClass (&wndclass)) // 注册窗体类别
    {
        MessageBox (  NULL, TEXT ("This program requires Windows NT!"),
        szAppName, MB_ICONERROR) ;
        return 0 ;
    }
    // 窗体建立函数
    hwnd = CreateWindow( szAppName,  // window class name
                        TEXT ("The Hello Program"), // window caption
                        WS_OVERLAPPEDWINDOW,  // window style  窗体风格
                        CW_USEDEFAULT,  // initial x position
                        CW_USEDEFAULT,  // initial y position
                        CW_USEDEFAULT,  // initial x size
                        CW_USEDEFAULT,  // initial y size
                        NULL, // parent window handle
                        NULL,  // window menu handle
                        hInstance,  // program instance handle
                        NULL) ; // creation parameters
    // 窗体显示,根据iCmdShow来确定如何显示窗口,最大化,最小化还是一般化
    ShowWindow (hwnd, iCmdShow) ;
    UpdateWindow (hwnd) ;
    // 进入消息循环 GetMessage收到WM_QUIT消息时返回0,其余非零
    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage (&msg) ; // 消息翻译(主要处理一些键盘消息)
        DispatchMessage (&msg) ; // 消息分发进入WndProc函数进行消息处理
    }
    return msg.wParam ;
}

// 消息处理函数
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC  hdc ;
    PAINTSTRUCT ps ;
    RECT rect ;
    switch (message)
    {
    case WM_CREATE:
        PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
        return 0 ;
    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps) ;
        GetClientRect (hwnd, &rect) ;
        DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
        EndPaint (hwnd, &ps) ;
        return 0 ;
    case WM_DESTROY:
        PostQuitMessage (0) ;
        return 0 ;
        }
return DefWindowProc (hwnd, message, wParam, lParam) ; // 传给默认的消息处理函数
}

// 重点消息结构体的定义
typedef struct tagMSG
{
    HWND hwnd;      // 接收消息的窗体
    UINT message;   // 消息类型 在下面讲 WM_ 等
    WPARAM wParam;  // 32位信息参数,根据消息类型进行填充
    LPARAM lParam;  // 32位信息参数,同上
    DWORD time;     // 信息放入消息队列的时间
    POINT pt;       // 信息放入消息队列的鼠标坐标
}

消息可分为序列化消息和非序列化消息
序列化消息:可以理解为外部呼叫程序。如:键盘消息(WM_CHAR)、鼠标消息(WM_KEYDOWN、WM_KEYUP、WM_MOUSEMOVE)等
非序列化消息:可以理解为程序自己呼叫自己。如:CreateWindow时发送WM_CREATE,UpdateWindow时发送WM_PAINT

运行后如下:

这里写图片描述

消息分类

种类大体上分为三种消息:窗口消息、命令消息、控件通知消息
窗口消息:指操作系统和控制其他窗口的窗口所使用的消息。
命令消息:用来处理从一个窗口发生到另一个窗口的用户请求。
控件通知消息:一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的控件
如按钮、列表框、组合框、编辑框等。当用户与控件窗口交互的时候控件通知消息就会从控件窗口发送到它的主窗口。

按钮控件
BN_CLICKED          用户单击了按钮
BN_DISABLE          按钮被禁止
BN_DOUBLECLICKED    双击
BN_HILITE           加亮了按钮
BN_PAINT            按钮需要重绘
BN_UNHILITE         取消加亮

组合框控件
CBN_CLOSEUP         列表框被关闭
CBN_DBLCLK          用户双击了一个字符串
CBN_DROPDOWN        列表框被拉出
CBN_EDITCHANGE      修改编辑框文本
CBN_EDITUPDATE      文本更新
CBN_ERRSPACE        内存不足
CBN_KILLFOCUS       组合框失去输入焦点
CBN_SELCHANGE       选择了一项
CBN_SETFOCUS        获得输入焦点

编辑框控件
EN_CHANGE           文件已更新
EN_VSCROLL          用户点击了垂直滚动条消息
...

列表框控件
LBN_DBLCLK          用户双击了一项
LBN_KILLFOCUS       列表框失去焦点
...
  • 0x0000 空消息

    指WM_NULL
    
  • 0x0001–0x0087 窗口消息

    WM_CREATE、WM_DESTROY、WM_MOVE、WM_SIZEWAIT...
    
  • 0x00A0–0x00A9 非客户区消息

    WM_NCMOUSEMOVE、WM_NCLBUTTONDOWN、WM_NCLBUTTONUP...       
    
  • 0x0100–0x0108 键盘消息

    WM_KEYDOWN、WM_KEYUP、WM_CHAR...      
    
  • 0x0111–0x0126 菜单消息

    WM_COMMAND、WMSYSCOMMAND、WM_TIMER、WM_HSCROLL...
    
  • 0x0132–0x0138 颜色控制消息

    WM_CTLCOLORMSGBOX、WM_CTLCOLOREDIT、WM_CTLCOLORBTN...
    
  • 0x0200–0x020A 鼠标消息

    WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP...
    
  • 0x0211–0x0213 菜单循环消息

    WM_ENTERMENULOOP、WM_EXITMENULOOP
    
  • 0x0220–0x0230 多文档消息

    WM_MDICREATE、WM_MDIACTIVATE、WM_MDIDESTROY
    
  • 0x0400 WM_USER
    指应用程序自定义私有消息
  • 0x8000 WM_APP

MFC消息机制

对于MFC框架来建议看侯捷先生的一本书《深入浅出MFC》。可先看基础部分

  • MFC程序的初始化过程
  • RTTI执行时期类型识别
  • Dynamic Creation动态生成
  • Perisistece 永续留存
  • Message Mapping 消息映射
  • Message Routing 消息绕行

由前两节的基础。再根据深入浅出MFC中的消息映射。一起来看看MFC是怎么处理消息映射

在MFC程序中我们经常看到构建MFC消息映射的宏如下:
DECLARE_MESSAGE_MAP

struct AFX_MSGMAP
{
    AFX_MSGMAP* pBaseMessageMap;
    AFX_MSGMAP_ENTRY* lpEntries;
};

struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
    UINT nMessage; // windows message
    UINT nCode; // control code or WM_NOTIFY code
    UINT nID; // control ID (or 0 for windows messages)
    UINT nLastID; // used for entries specifying a range of control id's
    UINT nSig; // signature type (action) or pointer to message #
    AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (CCmdTarget::*AFX_PMSG)(void);

#define DECLARE_MESSAGE_MAP() \
    static AFX_MSGMAP_ENTRY _messageEntries[]; \
    static AFX_MSGMAP messageMap; \
    virtual AFX_MSGMAP* GetMessageMap() const;

以上宏构建消息映射网络
下面宏填充消息映射网络的内容
消息映射宏
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_CONTROL
ON_MESSAGE
END_MESSAGE_MAP

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
    AFX_MSGMAP* theClass::GetMessageMap() const \
    { return &theClass::messageMap; } \
    AFX_MSGMAP theClass::messageMap = \
        { &(baseClass::messageMap), \
        (AFX_MSGMAP_ENTRY*) &(theClass::_messageEntries) }; \
    AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
    {
#define ON_COMMAND(id, memberFxn) \
        { WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
        { 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
    };
// 以下结构代表消息处理函数的类型。
    enum AfxSig
    {
        AfxSig_end = 0, // [marks end of message map]
        AfxSig_vv, 
    };

通过以上宏及构建出了如下的网络

这里写图片描述

关于ON_COMMAND及AfxSig定义如下图

这里写图片描述

这里写图片描述

将MFC中的框架类库串起来就会构成这样的一大张网络如下

这里写图片描述

目前我们网路搭建起来了,即相应的消息可在网路中查找想应的处理函数。万事俱备只欠东风。我们来了解一下消息泵的推动点
及如SDK程序中的消息分流器。WinProc。

  1. 首先在进入MFC入口点时。MFC调用了一个全局初始化函数,这部分做了一件事,及注册了一个消息回调函数AfxWndProc。同SDK程序中的WndProc。如下

    BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
    {
        ...
        // register basic WndClasses (
        WNDCLASS wndcls;
        wndcls.lpfnWndProc = AfxWndProc;
        // Child windows - no brush, no icon, safest default class styles
        ...
        wndcls.lpszClassName = _afxWnd;
        if (!::RegisterClass(&wndcls))
            return FALSE;
        // Control bar windows
        ...
        wndcls.lpszClassName = _afxWndControlBar;
        if (!::RegisterClass(&wndcls))
            return FALSE;
        // MDI Frame window (also used for splitter window)
        ...
        if (!RegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
            return FALSE;
        // SDI Frame or MDI Child windows or views - normal colors
        ...
        if (!RegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
            return FALSE;
        ...
    }
    
  2. AfxWndProc如下:

    LRESULT CALLBACK AFX_EXPORT
    AfxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        CWnd* pWnd;
        pWnd = CWnd::FromHandlePermanent(hWnd);
        ASSERT(pWnd != NULL);
        ASSERT(pWnd->m_hWnd == hWnd);
        LRESULT lResult = _AfxCallWndProc(pWnd, hWnd, message, wParam, lParam);
        return lResult;
    }
    
    LRESULT PASCAL _AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam)
    {
        LRESULT lResult;
        ...
        TRY
        {
            ...
            lResult = pWnd->WindowProc(message, wParam, lParam);
        }
            ...
            return lResult;
    }
    
  3. 消息分发获取函数

    在应用程序调用CWinAPP::Run()时,里面调用::DispatchMessage()即将消息推送至CWnd::WindowProc()函数。
    WindowProc(),即根据所注册的消息大型网络进行找对应的消息响应函数执行。
    

猜你喜欢

转载自blog.csdn.net/zgl390963305/article/details/80550005
今日推荐