您的位置:首页 > 其它

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

2016-03-05 16:59 417 查看
网上面很多关于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在对话框应用程序里的流动路径。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  MFC WM_COMMAND