MFC中关于基于对话框的应用程序的WM_COMMAND消息的流动路径

网上面很多关于WM_COMMAND消息在基于文档视图模型的APP中的流动方向的讲解,但是我在项目中做的都是基于对话框的APP,所以这里就只介绍WM_COMMAND在对话框中的流动方向。

如果对话框里有个按钮,鼠标在按钮上点击后所发生的事情如下:

1.鼠标在按钮上点击一下。

2.系统产生WM_LBUTTONDOWN消息和WM_LBUTTONUP消息。

说明:如果这两个消息都在在按钮上触发的(即使在按钮上按下后再把鼠标移到别处,鼠标一直是按下的,再移回到按钮上放开鼠标),那么这两个消息在defwindowproc中就会导致按钮对象产生一个WM_COMMAND消息,通知父窗口按钮被点击了。

3.这两个消息会被GetMessage()函数从应用程序的线程消息队列中取出来。

4.消息取出来后首先会调用PreTranslateMessage()函数,在这个函数里可以拦截WM_LBUTTONDOWN消息和WM_LBUTTONUP消息,如果本窗口不做处理,则会先取得本窗口的父窗口,再调用父窗口的PreTranslateMessage()函数。直到父窗口为空。(这里的WM_LBUTTONDOWN消息和WM_LBUTTONUP消息还是之前的系统发送给按钮的,即使调用了父窗口的PreTranslateMessage()函数,其实就是说父窗口在处理子窗口的消息,给了父窗口处理子窗口消息的机会)。

说明:

PretranslateMessage 的实现,不得不谈到MFC消息循环的实现。MFC通过CWinApp类中的Pumpmessage函数实现消息循环,但是实际的消息循环代码位于 CWinThread中,CWinApp只是从CWinThread继承过来。其简化后的代码大概如下: 
  BOOL CWinThread::PumpMessage() 
  { 
  _AFX_THREAD_STATE *pState = AfxGetThreadState(); 
   
  ::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)) 
   
  if (!AfxPreTranslateMessage(&(pState->m_msgCur))) 
  { 
  ::TranslateMessage(&(pState->m_msgCur)); 
  ::DispatchMessage(&(pState->m_msgCur)); 
  } 
  return TRUE; 
  } 
   可以看到,PumpMessage在实际的TranslateMessage和DispatchMessage发生之前会调用 AfxPreTranslateMessage,AfxPreTranslateMessage又会调用 CWnd::WalkPreTranslateTree(虽然也会调用其他函数,但是这个最为关键),其代码如下: 
  BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg) 
  { 
  ASSERT(hWndStop == NULL || ::IsWindow(hWndStop)); 
  ASSERT(pMsg != NULL); 
   
  // walk from the target window up to the hWndStop window checking 
  // if any window wants to translate this message 
   
  for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd)) 
  { 
  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); 
  if (pWnd != NULL) 
  { 
  // target window is a C window 
  if (pWnd->PreTranslateMessage(pMsg)) 
  return TRUE; // trapped by target window (eg: accelerators) 
  } 
   
  // got to hWndStop window without interest 
  if (hWnd == hWndStop) 
  break; 
  } 
  return FALSE; // no special processing 
  } 
   
  可以看到,代码还是很直接的。从接受到消息的窗口层层往上遍历,并调用PretranslateMessage看是否返回TRUE,是则结束,否则继续。 
  这里有一个地方非常关键:CWnd *pWnd = CWnd::FromHandlePermanent(hWnd) 这一句代码从当前AfxModuleThreadState拿到Permanent句柄表,从而找到hWnd对应的CWnd 
5.如果这两个消息在本窗口和父窗口的PreTranslateMessage()函数里都没有处理,那么接下来这两个消息就会交给这两个消息的窗口处理函数(本窗口的窗口消息处理函数)。

6.一般情况下,在窗口处理函数里不会对这两个消息进行处理(如果处理的话就不会再产生WM_COMMAND消息了),那么窗口处理函数就会把这两个消息再交给DefWindowProc(默认窗口处理函数)来处理这两个消息。按钮的默认消息处理函数(DefWindowProc)会根据这两个消息来产生一个WM_COMMAND消息,并把这个消息发送给按钮的父窗口。

上面所说的情况就是WM_COMMAND消息的产生过程,是以按钮为例子来说明的。


下面再来说WM_COMMAND在发送给父窗口后的处理过程(父窗口为对话框)。

1.对话框遍历自己的消息映射表(从对话框的类开始再到对话框的父类,一直这样往上遍历),看自己和自己的父类(而不是父窗口)能不能处理。

2.对话框自己不能处理的话,再交给对话框的父窗口(而不是父类)来处理。这个父窗口怎么处理就看父窗口有没有实现OnCmdMsg()这个虚拟函数,通过这个虚拟函数父窗口可以自己定义WM_COMMAND的流动方向。

3.如果父窗口也没有处理的话(没有在消息映射表中匹配到),那么就交给应用程序类处理了。也是会调用CWinApp的OnCmdMsg()函数来处理。在这里应用程序类也可以实现自己的OnCmdMsg()函数来定义WM_COMMAND的流动方向。

4.如果应用程序类也没有处理的话,那么这个WM_COMMAND消息就会被忽略掉,不会被处理了。

MFC原代码如下:

BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,
	AFX_CMDHANDLERINFO* pHandlerInfo)
{
	if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
		return TRUE;

	if ((nCode != CN_COMMAND && nCode != CN_UPDATE_COMMAND_UI) ||
			!IS_COMMAND_ID(nID) || nID >= 0xf000)
	{
		// control notification or non-command button or system command
		return FALSE;       // not routed any further
	}

	// if we have an owner window, give it second crack
	CWnd* pOwner = GetParent();
	if (pOwner != NULL)
	{
		TRACE(traceCmdRouting, 1, "Routing command id 0x%04X to owner window.\n", nID);

		ASSERT(pOwner != this);
		if (pOwner->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
			return TRUE;
	}

	// last crack goes to the current CWinThread object
	CWinThread* pThread = AfxGetThread();
	if (pThread != NULL)
	{
		TRACE(traceCmdRouting, 1, "Routing command id 0x%04X to app.\n", nID);

		if (pThread->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
			return TRUE;
	}

	TRACE(traceCmdRouting, 1, "IGNORING command id 0x%04X sent to %hs dialog.\n", nID,
			GetRuntimeClass()->m_lpszClassName);

	return FALSE;
}


以上就是WM_COMMAND在对话框应用程序里的流动路径。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值