VC++/MFC消息映射机制(1):MFC消息映射原理

VC++/MFC消息映射机制(1):模仿MFC的消息映射原理

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)

《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg

若对C++语法不熟悉,建议参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。

1、消息映射:就是把指定的消息交给指定的函数进行处理的方法,这样就形成了一个<消息,处理函数>对。
2、本文有时会使用<M,F>表示<消息,处理函数>对。
一、共用体(union)的使用
1、共用体可以实现以下两个功能(详见示例说明)。
1)、调用函数时不需要知道函数的名称(通过函数指针调用),以及确定调用该函数时需要传递什么类型的形参(即可以确定函数原型)。
2)、在类继承体系中实现类似虚函数的功能。
2、语法问题(指向类成员的指针):使用指向类成员的函数指针时,必须通过类的对象或指向类的指针间接使用,比如class A{public:void f(){}}; A m; A *pa=&m; void (A::*pf)()=&A::f; 则应这样通过pf调用f,即(ma.*pf)()或(pa->*pf)();是正确的,但不能直接使用pf调用成员函数f,即(*pf)();或(A::*pf)();错误。

在这里插入图片描述

二、处理单个<消息,处理函数>对的消息映射原理
示例3.6:简单的消息映射原理(本示例只能处理单个的<消息,处理函数>对,以下程序为MFC程序)

本示例需要明白C++语法原理:指针与类型的关系
#include <afxwin.h>   
class A:public CWinApp{public:   BOOL InitInstance(); }; 
class B:public CFrameWnd{public:  B(){Create(NULL,_T("HYONG"),WS_OVERLAPPEDWINDOW);}};  

//❷、使用结构体类型建立<消息,处理函数>对。
typedef void (*PF)();
struct S{UINT msg;UINT msgid; PF pf;};
LRESULT f1(WPARAM w, LPARAM l){::MessageBox(0,"C","D",0);return 0;} //用于处理消息的函数
LRESULT f(WPARAM w, LPARAM l){ ::MessageBox(0,"A","B",0);return 0;} //用于处理消息的函数
//❸、使用结构体类型的数组关联不同的<消息,处理函数>对。以下代码可使用宏进行封装(包装)
/*以下每个一个数组元素都指定了一个<M,F>对,比如ss[0]代表一对<M,F>,ss[1]又表示一对<M,F>,程序员只需把需要处理的<消息,处理函数>对添加到以下数组中即可实现消息映射原理(即把指定的消息使用指定的函数进行处理),也就是说数组ss中的数据,是由程序员指定的。*/
const S ss[]={{WM_LBUTTONDOWN,1,PF(f1)},{WM_RBUTTONDOWN,2,PF(f)}, {0,0,(PF)0}}; //重点数组

/*❹、使用共用体间接调用消息处理函数。以下共用体用于讲解目的,只列出了一部分消息处理函数可能出现的原型。MFC源代码的内容是很长的。*/
union UN{PF pf; LRESULT (*pf_0)(WPARAM,LPARAM);LRESULT (*pf_1)(WPARAM,LPARAM);};
UN meff;  

LRESULT CALLBACK g(HWND h1,int msg, WPARAM w, LPARAM l){  //自定义的过程函数。
	switch (msg) {
		case WM_LBUTTONDOWN:
{meff.pf=ss[0].pf;  //初始化共用体变量meff,此时ss[0].pf指向的函数是f1。
meff.pf_1(w,l); /*❹、使用共用体间接调用消息处理函数f1。程序员可能认为可以在此处直接调用消息处理函数f1不就行了吗?何必这么麻烦?但是在MFC源码中,这部分内容是对程序员隐藏的,源码并不知道程序员向数组ss中添加的“<消息,处理函数>对”中的处理函数的名称是什么,因此不可能直接对“<消息,处理函数>对”中的处理函数进行调用,而只能使用共用体的形式进行间接调用。*/
break;}
		case WM_RBUTTONDOWN:
			{meff.pf=ss[1].pf; //使共用体变量meff.pf指向ss[1].pf指向的函数是f。
				meff.pf_0(w,l); //调用f函数,处理鼠标右键消息
				break;}
		case WM_DESTROY: {	::PostQuitMessage(0);	break; }
		default:return ::DefWindowProc(h1, msg, w, l);	}
	return 0;	}
BOOL A::InitInstance(){   m_pMainWnd=new B();  
m_pMainWnd->ShowWindow(m_nCmdShow);	m_pMainWnd->UpdateWindow();
		//❶、重新设置MFC的过程函数为自定义的函数。
		SetWindowLongPtr(m_pMainWnd->m_hWnd,GWLP_WNDPROC,(LONG)g);  //重置过程函数为函数g。
		return TRUE;}
A ma;    

按下鼠标左键后弹出的消息框如下图(省略主窗口):
在这里插入图片描述
程序算法步骤详解:
1、使用SetWindowLongPtr函数重新设置MFC程序的过程函数。
2、把需要处理的<消息,处理函数>对(即<M,F>),抽像为一个类型,假设使用结构体类型S进行表示,那么每个结构体类型变量都会保存有一个相对应的<M,F>。比如:
typedef void (*PF)();
struct S{UINT msg;UINT msgid; PF pf;};
1)、msg表示需要处理的消息。
2)、msgid用于标示该结构体变量的一个id符号。该成员在本例无用处,但在后面会有用。
3)、pf表示用于处理消息msg的处理函数。
4)、为什么pf的类型是PF:因为消息处理函数的原型并不是全部一致的,在进行消息映射时应使用相同的函数原型形式(即PF的形式)以便对消息处理函数进行统一管理,因此在使用消息处理函数初始化该成员时需要把消息处理函数强制转换为PF类型。
3、创建一个结构体类型S的数组用于保存不同的<M,F>对,该数组就是程序员把消息指定给自定义函数进行处理的地方。比如:
LRESULT f1(WPARAM w, LPARAM l){return 0;} //处理消息的函数f1
LRESULT f(WPARAM w, LPARAM l){return 0;} //处理消息的函数f
const S ss[]={{WM_LBUTTONDOWN,1,PF(f1)},{WM_RBUTTONDOWN,2,PF(f)}, {0,0,(PF)0}};
1)、数组ss保存有两个<M,F>对,即处理鼠标左键按下消息的<WM_LBUTTONDOWN,f1>和处理鼠标右键按下消息的<WM_RBUTTONDOWN,f>。
2)、若程序员需要把其他消息使用另外的函数进行处理,则只需把相应的<消息,处理函数>对,添加到数组ss中即可,这样就实现了消息的映射。
3)、完成以上步骤之后,则在过程函数中接收到需要处理的消息时,只需调用“<M,F>”中的处理函数F处理该消息即可。问题的关键是怎样调用“处理函数F”。
4、使用共用体间接调用消息处理函数:
怎样调用相关联的消息处理函数:因为MFC的源码实现的消息映射是向程序员隐藏了的,那么在调用消息处理函数时,MFC源码肯定是不知道程序员自定义的“消息处理函数”的名称的,这就意味着,不能在源码中直接调用消息处理函数,而只能间接的调用类似以上数组ss中的结构体S中的成员pf,即只能这样调用消息处理函数ss[1].pf();但因为pf的原型与消息处理函数的原型并不相同(本例pf与f原型就不一致),这就可能会产生错误,为了解决函数原型的问题,可以使用共用体类型的成员保存消息处理函数的原型,然后使用共用体成员间接调用消息处理函数。比如:

union UN{PF pf; LRESULT (*pf_0)(WPARAM,LPARAM);LRESULT (*pf_1)(WPARAM,LPARAM);};
   UN meff;  
meff.pf=ss[0].pf;  //初始化共用体变量meff,其中ss[0].pf指向的函数是f1。
meff.pf_0(w,l);   //通过共用体成员pf_0间接调用消息处理函数f1。

1)、以上共用体是用于讲解目的,只列出了一部分消息处理函数可能出现的原型。MFC源代码的内容是很长的(因为包括了所有可能的消息处理函数的原型)。
2)、注意共用体的特点,成员pf与pf_0和pf_1是共用的同一内存段,因此访问共用体变量中的任一成员(比如pf、pf_0、pf_1),他们的值都是一样的。但除pf之外的其他成员保存了“消息处理函数”可能出现的各种原型,这就解决了消息处理函数原型不一致的问题。
3)、共用体中的第一个成员pf主要是用于进入(或关联)结构体S而使用的,或者说用于初始化共用体成员变量的,否则会因为类型不相同而无法初始化。比如UN meff; meff.pf=ss[0].pf; 此时便可在UN中寻找与ss[0].pf相对应的消息处理函数原型相同的成员调用消息处理函数,在本例中ss[0].pf指向的消息处理函数是f1,在UN中与f1原型相同的成员是pf_0和pf_1,因此可使用其中任意一个间接的调用消息处理函数f1,即可以这样调用meff.pf_0(w,l); 其中w和l是假设的两个正确的实参。

三、实现处理多个<消息,处理函数>对的消息映射原理
程序算法如下:

在这里插入图片描述

示例3.7:处理多个消息的消息映射原理
注:以下示例需要结合上一示例进行阅读
在这里插入图片描述
在这里插入图片描述
程序运行结果如下:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
本文作者:黄邦勇帅(原名:黄勇)

在这里插入图片描述

  • 11
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值