MFC - 02 钩子、窗口、消息

 

钩子

win32 技术。钩子有优先钩取消息的权力,消息产生后会先被钩子钩走,钩子处理完后再把消息返回回来。

  1. 创建钩子
    WINUSERAPI
    HHOOK
    WINAPI
    SetWindowsHookExA(
        _In_ int idHook,         // 钩子类型,不同类型的钩子处理不同消息。 WH_CBT:专注窗口创建消息,MFC多用
        _In_ HOOKPROC lpfn,      // 钩子处理函数
        _In_opt_ HINSTANCE hmod, // 目标应用程序实例句柄,为空时钩取所有实例的消息
        _In_ DWORD dwThreadId);  // 目标线程ID,为0时钩取所有线程
  2. 钩子处理函数
    LRESULT CALLBACK HOOKPROC(
        int code,      // 钩子码,与钩子类型对应。 HCBT_CREATEWND 与 WH_CBT 对应
        WPARAM wParam, // 在WH_CBT类型钩子中,是刚刚创建成功窗口句柄
        LPARAM lParam  // ...
    );
  3. 更改窗口 处理函数/样式
    WINUSERAPI
    LONG_PTR
    WINAPI
    SetWindowLongPtrA(
        _In_ HWND hWnd,  // 窗口句柄
        _In_ int nIndex, // 更改窗口的哪一部分 GWLP_WNDPROC:更改处理函数
        _In_ LONG_PTR dwNewLong); // 新的窗口处理函数地址

 

 

 简单MFC程序:

#include <afxwin.h>

// 框架类
class CMyFrameWnd : public CFrameWnd
{
public:
};

// 应用程序类
class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

 单文档程序:

  1. 框架类
  2. 应用程序类
  3. 文档类
  4. 视图类

 

使用到的类:

 MFC 窗口创建过程:

  1. 加载菜单
  2. 调用 CWmd::CreateEx 函数创建窗口
    调用 PreCreateWindow 函数设计和注册窗口类调用 AfxDeferRegisterClass函数,在这个函数中设计窗口类:
    WNDCLASS wndcls; // 设计窗口类
    ...
    wndcls.lpfnWndProc = DefWindowProc; // 定义窗口的处理函数为DefWindowProc
    调用 _AfxRegisterWithIcon 函数
        在函数内部,加载图标,并调用AfxRegisterClass 函数,
            在函数内部,调用 ::RegisterClass win32API函数注册窗口类

MFC 消息

 MFC 消息处理:

  1. 当收到消息时,进入 AfxWndProc 函数
  2. AfxWndProc 函数根据消息的窗口句柄,查询对应框架类对象的地址(pFrame)
  3. 利用框架类对象地址(pFrame)调用框架类成员虚函数 WindowProc,完成消息的处理

重写消息处理虚函数

使用重写虚函数的方式处理消息并不是使用消息映射机制

#include <afxwin.h>

class CMyFrameWnd : public CFrameWnd
{
public:
	virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam);
};

LRESULT CMyFrameWnd::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_CREATE:
	{
		AfxMessageBox("create");
		break;
	}
	default:
		break;
	}

	return CFrameWnd::WindowProc(msg, wParam, lParam);
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

MFC 消息映射机制

 在不重写WindowProc 虚函数的情况下仍能处理消息

必备条件:

  1. 类内添加声明宏
    DECLARE_MESSAGE_MAP()
  2. 类外添加实现宏
    BEGIN_MESSAGE_MAP(theClass, baseClass)
    END_MESSAGE_MAP()

 当一个类具备上述两个要件,这个类就可以按照消息映射机制处理消息。

消息映射机制的具体实施,以 WM_CREATE 为例:

  1. BEGIN_MESSAGE_MAP(theClass, baseClass)END_MESSAGE_MAP() 之间添加 ON_MESSAGE(WM_CREATE, OnCreate)
  2. CMyFrameWnd 类内添加 OnCreate 函数的声明和定义

 

#include <afxwin.h>

class CMyFrameWnd : public CFrameWnd
{
	DECLARE_MESSAGE_MAP()
public:
	LRESULT OnCreate(WPARAM, LPARAM);
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_MESSAGE(WM_CREATE, OnCreate)
END_MESSAGE_MAP()

LRESULT CMyFrameWnd::OnCreate(WPARAM wParam, LPARAM lParam)
{
	AfxMessageBox("消息映射机制 WM_CREATE");
	return 0;
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

 

消息分类

定义了特殊的宏用于处理消息

  1. 标准windows消息
    ON_WM_XXX 大多数消息
    处理每种消息的回调函数参数与返回值都不同,具体查询手册
  2. 通用消息
    ON_MESSAGE用于处理自定义消息
    回调参数类型:LRESULT fun(WPARAM, LPARAM);
  3. 命令消息
    ON_COMMAND

处理 WM_CREATE 的例子

#include <afxwin.h>

class CMyFrameWnd : public CFrameWnd
{
	DECLARE_MESSAGE_MAP()
public:
	int OnCreate(LPCREATESTRUCT);
	LRESULT OnCreate(WPARAM, LPARAM); // ON_MESSAGE 对应的回调函数签名,没有对参数进行封装
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	//ON_MESSAGE(WM_CREATE, OnCreate)
	ON_WM_CREATE() // 与上一行类似,但是回调函数名称固定且处理每个消息的函数的参数与返回值不同
END_MESSAGE_MAP()

int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs)
{
	AfxMessageBox("消息映射机制 WM_CREATE");
	return CFrameWnd::OnCreate(pcs);
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

 

 MFC 菜单

 菜单对应类型:

  1. win32:HMENU
  2. MFC:CMenu 类对象,内部包含变量 m_hMenu

 

  1. 添加菜单资源
  2. 将菜单设置到窗口
    1. 利用 pFrame 调用Create函数时传参
      // 在MFC中消息没有被处理的菜单项会被置为灰色
      pFrame->Create(NULL, "MFCCreate", WS_OVERLAPPEDWINDOW, CFrameWnd::rectDefault, NULL, (char *)IDR_MENU1);
    2. 在处理框架窗口的WM_CREATE 消息时
      CMenu menu;
      menu.LoadMenu(...);
      
      int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs)
      {
      	this->menu.LoadMenuA((LPCTSTR)IDR_MENU1);
      	this->SetMenu(&menu);
      	return CFrameWnd::OnCreate(pcs);
      }
  3. 消息处理
    消息映射宏:ON_COMMAND(菜单项ID,处理消息的函数名)
    #include <afxwin.h>
    
    #include "resource.h"
    
    class CMyFrameWnd : public CFrameWnd
    {
    	DECLARE_MESSAGE_MAP()
    public:
    	afx_msg int OnCreate(LPCREATESTRUCT);
    	afx_msg void OnFileNew();
    private:
    	CMenu menu;
    };
    BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
    	ON_WM_CREATE()
    	ON_COMMAND(ID_FILE_NEW, OnFileNew)
    END_MESSAGE_MAP()
    
    int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs)
    {
    	this->menu.LoadMenuA((LPCTSTR)IDR_MENU1);
    	this->SetMenu(&menu);
    	return CFrameWnd::OnCreate(pcs);
    }
    
    void CMyFrameWnd::OnFileNew()
    {
    	AfxMessageBox("新建");
    }
    
    class CMyWinApp : public CWinApp
    {
    public:
    	virtual BOOL InitInstance();
    };
    
    BOOL CMyWinApp::InitInstance()
    {
    	CMyFrameWnd* pFrame = new CMyFrameWnd;
    	this->m_pMainWnd = pFrame;
    	pFrame->Create(NULL, "MFCCreate");
    	pFrame->ShowWindow(SW_SHOW);
    	pFrame->UpdateWindow();
    	return TRUE;
    }
    
    CMyWinApp theApp;

命令消息处理顺序

实现消息映射的宏展开后发现,声明了一个由消息和处理函数地址组成的数组,还有一个结构体,结构体内两个指针,指向父结构体和数组。

但是 WM_COMMAND 消息的处理路径不一样,在一系列的框架类处理映射中没找到后,还会到应用程序类中查找消息处理映射。顺序:文档类 -> 视图类 -> 框架类 -> 应用程序类;这四种类都可以处理 WM_COMMAND 消息,但其他消息不是。

 

设置菜单项状态

win32

  • WM_INITMENUPOPUP (菜单激活但是还没有显示)
  • ::CheckMenuItem / ::EnableMenuItem

MFC

  • ON_WM_INITMENUPOPUP (专用宏)
  • CMenu::CheckMenuItem / CMenu::EnableMenuItem
菜单项打勾
#include <afxwin.h>

#include "resource.h"

class CMyFrameWnd : public CFrameWnd
{
	DECLARE_MESSAGE_MAP()
public:
	afx_msg int OnCreate(LPCREATESTRUCT);
	afx_msg void OnInitMenuPopup(CMenu *pPopup, UINT nPos, BOOL i);
private:
	CMenu menu;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_WM_CREATE()
	ON_WM_INITMENUPOPUP()
END_MESSAGE_MAP()

// 在指定菜单项上打勾
void CMyFrameWnd::OnInitMenuPopup(CMenu* pPopup, UINT nPos, BOOL i)
{
	pPopup->CheckMenuItem(ID_FILE_NEW, MF_CHECKED);
}

int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs)
{
	this->menu.LoadMenuA((LPCTSTR)IDR_MENU1);
	this->SetMenu(&menu);
	return CFrameWnd::OnCreate(pcs);
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

 

右键菜单(上下文菜单)

  •  win32
    • WM_CONTEXTMENU
    • ::TrackPopupMenu
  • MFC
    • ON_WM_CONTEXTMENU
    • CMENU::TrackPopupMenu
#include <afxwin.h>

#include "resource.h"

class CMyFrameWnd : public CFrameWnd
{
	DECLARE_MESSAGE_MAP()
public:
	afx_msg int OnCreate(LPCREATESTRUCT);
	afx_msg void OnContextMenu(CWnd* pWnd, CPoint pt);
private:
	CMenu menu;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_WM_CREATE()
	ON_WM_CONTEXTMENU()
END_MESSAGE_MAP()

void CMyFrameWnd::OnContextMenu(CWnd* pWnd, CPoint pt)
{
	// 获取顶层菜单中第一个弹出式菜单
	//HMENU hPopup = ::GetSubMenu(menu.m_hMenu, 0);
	CMenu* pPopup = menu.GetSubMenu(0);
	// 右键弹出指定弹出菜单
	//::TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_TOPALIGN,
	//	pt.x, pt.y, 0, this->m_hWnd, NULL);
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, this);
}

int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs)
{
	this->menu.LoadMenuA((LPCTSTR)IDR_MENU1);
	this->SetMenu(&menu);
	return CFrameWnd::OnCreate(pcs);
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

 

顶层菜单:   鼠标右键菜单:

 

MFC 工具栏

工具栏中按钮在菜单项一般都绑定着用。

相关类

  • CToolBarCtrl - 父类 CWnd
    封装了关于工具栏控件的各种操作,不代表工具栏,代表的时工具栏中按钮
  • CToolBar - 父类 CControlBar
    封装了关于工具栏的操作,以及和框架窗口的关系。代表工具栏容器

工具栏使用

  1. 添加工具栏资源

    先添加一个菜单栏,一般工具栏和菜单栏功能绑定。再创建工具栏资源。按钮与菜单项id一致,当二者被点击时都发出 ON_COMMAND 消息,这样二者就被绑定了。

    又创建了按钮2,不与菜单项绑定
  2. 创建工具栏 CToolBar::CreateEx
    virtual BOOL Create(CWnd* pParentWnd,
            DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP,
            UINT nID = AFX_IDW_TOOLBAR
        );
    
        virtual BOOL CreateEx(
            CWnd* pParentWnd,                                       // 父窗口对象
            DWORD dwCtrlStyle = TBSTYLE_FLAT,                       // 按键风格
            DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP, // 工具栏窗口
            CRect rcBorders = CRect(0, 0, 0, 0),
            UINT nID = AFX_IDW_TOOLBAR
        );
  3. 加载工具栏 CToolBar::LoadToolBar
  4. 设置工具栏的停靠,很多程序的工具栏都是能拽动的。


    红框标出的称为把手,创建toolbar时添加 CBRS_GRIPPER 风格,并与其他函数搭配:
    CToolBar::EnableDocking 指明工具栏想要放在哪里
    CFrameWnd::EnableDocking框架窗口指明允许放置在哪里
    前两个函数必须有交集,即同时满足工具栏的目标区域框架的约束区域
    CFrameWnd::DockControlBar设置工具栏具体临时停靠在交集的哪个位置

   

#include <afxwin.h>
#include <afxext.h>  // CToolBar 在此头文件中

#include "resource.h"

class CMyFrameWnd : public CFrameWnd
{
	DECLARE_MESSAGE_MAP()
public:
	afx_msg int OnCreate(LPCREATESTRUCT);
	afx_msg void OnContextMenu(CWnd* pWnd, CPoint pt);
	afx_msg void OnFileNew();
	afx_msg void OnSet();
private:
	CMenu menu;
	// 工具栏类位于 afxext.h 头文件中
	CToolBar toolbar;
};

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_WM_CREATE()
	ON_WM_CONTEXTMENU()
	ON_COMMAND(ID_FILE_NEW, OnFileNew)
	ON_COMMAND(ID_SET, OnSet)
END_MESSAGE_MAP()

void CMyFrameWnd::OnContextMenu(CWnd* pWnd, CPoint pt)
{
	// 获取顶层菜单中第一个弹出式菜单
	CMenu* pPopup = menu.GetSubMenu(0);
	// 右键弹出指定弹出菜单
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, this);
}

int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs)
{
	// 挂载 menu
	this->menu.LoadMenuA((LPCTSTR)IDR_MENU1);
	this->SetMenu(&menu);

	//挂载 Toolbar
	this->toolbar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP | CBRS_GRIPPER);
	this->toolbar.LoadToolBar(IDR_TOOLBAR1);

	// 工具栏停靠功能 https://docs.microsoft.com/zh-cn/cpp/mfc/docking-and-floating-toolbars?view=msvc-170
	this->toolbar.EnableDocking(CBRS_ALIGN_ANY);
	this->EnableDocking(CBRS_ALIGN_ANY);
	this->DockControlBar(&this->toolbar, AFX_IDW_DOCKBAR_BOTTOM);

	return CFrameWnd::OnCreate(pcs);
}

void CMyFrameWnd::OnFileNew()
{
	AfxMessageBox("ID_FILE_NEW");
}

void CMyFrameWnd::OnSet()
{
	AfxMessageBox("2号按钮被点击");
}

//----------------------------------------------------------

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

BOOL CMyWinApp::InitInstance()
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	this->m_pMainWnd = pFrame;
	pFrame->Create(NULL, "MFCCreate");
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}


CMyWinApp theApp;

     

 

posted @ 2022-08-02 08:51  某某人8265  阅读(219)  评论(0编辑  收藏  举报