【LibUIDK界面库系列文章】MDI菜单机制

来源:互联网 发布:淘宝客服组长的职责 编辑:程序博客网 时间:2024/04/29 20:39


作者:刘树伟


MDI结构中,当没有打开任何文档时,主框架有个默认的菜单。这个菜单,提供了基本的文件打开、关闭程序、帮助等功能,对应CFrameWnd::m_hMenuDefault,由CMDIFrameWnd::LoadFrame第一个参数决定,一般是由ID为IDR_MAINFRAME的菜单资源创建的。菜单的创建过程,在CFrameWnd::Create中,由CMDIFrameWnd::LoadFrame调用。请注意:CMDIFrameWnd是派生自CFrameWnd的,CMDIFrameWnd继承了CFrameWnd::m_hMenuDefault。
下面是菜单m_hMenuDefault的创建代码:

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName,
 DWORD dwStyle,
 const RECT& rect,
 CWnd* pParentWnd,
 LPCTSTR lpszMenuName,
 DWORD dwExStyle,
 CCreateContext* pContext)
{
 HMENU hMenu = NULL;
 if (lpszMenuName != NULL)
 {
  // load in a menu that will get destroyed when window gets destroyed
  HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
  if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)  // 创建菜单
  {
   TRACE0("Warning: failed to load menu for CFrameWnd.\n");
   PostNcDestroy();            // perhaps delete the C++ object
   return FALSE;
  }
 }

 m_strTitle = lpszWindowName;    // save title for later

 if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
  rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
  pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))  // 把菜单设置到新创建的窗口上。
 {
  TRACE0("Warning: failed to create CFrameWnd.\n");
  if (hMenu != NULL)
   DestroyMenu(hMenu);
  return FALSE;
 }

 return TRUE;
}

CFrameWnd::Create由CFrameWnd::LoadFrame调用

BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
 CWnd* pParentWnd, CCreateContext* pContext)
{
 // only do this once
 ASSERT_VALID_IDR(nIDResource);
 ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);

 m_nIDHelp = nIDResource;    // ID for help context (+HID_BASE_RESOURCE)

 CString strFullString;
 if (strFullString.LoadString(nIDResource))
  AfxExtractSubString(m_strTitle, strFullString, 0);    // first sub-string

 VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

 // attempt to create the window
 LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
 LPCTSTR lpszTitle = m_strTitle;
 if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault,
   pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
 {
  return FALSE;   // will self destruct on failure normally
 }

 // save the default menu handle
 ASSERT(m_hWnd != NULL);
 m_hMenuDefault = ::GetMenu(m_hWnd);     // 通过得到新创建窗口的菜单,为m_hMenuDefault赋值。

 // load accelerator resource
 LoadAccelTable(MAKEINTRESOURCE(nIDResource));

 if (pContext == NULL)   // send initial update
  SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

 return TRUE;
}


////////////////////////////////////////////////////////////

当打开了一种文档后,菜单栏中的菜单,会切换到与此文档相对应的菜单,每种文档类型,对应不同的菜单。这些文档菜单,对应CMDIChildWnd::m_hMenuShared,CMDIChildWnd也是派生自CFrameWnd,当然也继承了CFrameWnd::m_hMenuDefault,但CMDIChildWnd中的m_hMenuDefault,是无用的。

CMDIChildWnd::m_hMenuShared是在CMDIChildWnd::LoadFrame中被赋值的:
BOOL CMDIChildWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
  CWnd* pParentWnd, CCreateContext* pContext)
{
 // only do this once
 ASSERT_VALID_IDR(nIDResource);
 ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
 ASSERT(m_hMenuShared == NULL);      // only do once

 m_nIDHelp = nIDResource;    // ID for help context (+HID_BASE_RESOURCE)

 // parent must be MDI Frame (or NULL for default)
 ASSERT(pParentWnd == NULL || pParentWnd->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd)));
 // will be a child of MDIClient
 ASSERT(!(dwDefaultStyle & WS_POPUP));
 dwDefaultStyle |= WS_CHILD;

 // if available - get MDI child menus from doc template
 ASSERT(m_hMenuShared == NULL);      // only do once
 CMultiDocTemplate* pTemplate;
 if (pContext != NULL &&
  (pTemplate = (CMultiDocTemplate*)pContext->m_pNewDocTemplate) != NULL)
 {
  ASSERT_KINDOF(CMultiDocTemplate, pTemplate);
  // get shared menu from doc template
  m_hMenuShared = pTemplate->m_hMenuShared;   // 执行到这里赋值
  m_hAccelTable = pTemplate->m_hAccelTable;
 }
 else
 {
  TRACE0("Warning: no shared menu/acceltable for MDI Child window.\n");
   // if this happens, programmer must load these manually
 }

 CString strFullString, strTitle;
 if (strFullString.LoadString(nIDResource))
  AfxExtractSubString(strTitle, strFullString, 0);    // first sub-string

 ASSERT(m_hWnd == NULL);
 if (!Create(GetIconWndClass(dwDefaultStyle, nIDResource),
   strTitle, dwDefaultStyle, rectDefault,
   (CMDIFrameWnd*)pParentWnd, pContext))
 {
  return FALSE;   // will self destruct on failure normally
 }

 // it worked !
 return TRUE;
}

但CMDIChildWnd::m_hMenuShared不是自己通过LoadMenu之类初始化的,而是由CMultiDocTemplate::m_hMenuShared初始化的。CMultiDocTemplate::m_hMenuShared在CMultiDocTemplate::LoadTemplate()中,完成赋值:

void CMultiDocTemplate::LoadTemplate()
{
 CDocTemplate::LoadTemplate();

 if (m_nIDResource != 0 && m_hMenuShared == NULL)
 {
  HINSTANCE hInst = AfxFindResourceHandle(
   MAKEINTRESOURCE(m_nIDResource), RT_MENU);
  m_hMenuShared = ::LoadMenu(hInst, MAKEINTRESOURCE(m_nIDResource));
  m_hAccelTable =
   ::LoadAccelerators(hInst, MAKEINTRESOURCE(m_nIDResource));
 }

#ifdef _DEBUG
 // warnings about missing components (don't bother with accelerators)
 if (m_hMenuShared == NULL)
  TRACE1("Warning: no shared menu for document template #%d.\n",
   m_nIDResource);
#endif //_DEBUG
}

而m_nIDResource是在CMultiDocTemplate的构造函数中初始化,MDI App类的的InitInstance中,常见下面的代码:
 CMultiDocTemplate* pDocTemplate;
 pDocTemplate = new CMultiDocTemplate(
  IDR_MFCMDITYPE,    // 这个值,就用来初始化CMultiDocTemplate::m_nIDResource,最终用来生成文档菜单。
  RUNTIME_CLASS(CMFCMDIDoc),
  RUNTIME_CLASS(CChildFrame), // custom MDI child frame
  RUNTIME_CLASS(CMFCMDIView));

////////////////////////////////////////////////////////////////////////////////
不管是切换到文档对应的菜单,还是切换到主框架菜单,都是在CMDIChildWnd::OnUpdateFrameMenu中进行的:

void CMDIChildWnd::OnUpdateFrameMenu(BOOL bActivate, CWnd* pActivateWnd,
 HMENU hMenuAlt)
{
 CMDIFrameWnd* pFrame = GetMDIFrame();   // 得到主框架指针,通常为CMainFrame

 if (hMenuAlt == NULL && bActivate)
 {
  // attempt to get default menu from document
  CDocument* pDoc = GetActiveDocument();
  if (pDoc != NULL)
   hMenuAlt = pDoc->GetDefaultMenu();
 }

 // use default menu stored in frame if none from document
 if (hMenuAlt == NULL)
  hMenuAlt = m_hMenuShared;

 if (hMenuAlt != NULL && bActivate)      // 切换到文档菜单
 {
  ASSERT(pActivateWnd == this);

  // activating child, set parent menu
  ::SendMessage(pFrame->m_hWndMDIClient, WM_MDISETMENU,
   (WPARAM)hMenuAlt, (LPARAM)pFrame->GetWindowMenuPopup(hMenuAlt));
 }
 else if (hMenuAlt != NULL && !bActivate && pActivateWnd == NULL)  // 切换到主框架菜单
 {
  // destroying last child
  HMENU hMenuLast = NULL;
  ::SendMessage(pFrame->m_hWndMDIClient, WM_MDISETMENU,
   (WPARAM)pFrame->m_hMenuDefault, (LPARAM)hMenuLast);
 }
 else
 {
  // refresh MDI Window menu (even if non-shared menu)
  ::SendMessage(pFrame->m_hWndMDIClient, WM_MDIREFRESHMENU, 0, 0);
 }
}

需要特别注意的是:如果App类的InitInstance函数中,指定command line命令为FileNothing(默认为FileNew):
 CCommandLineInfo cmdInfo;
 ParseCommandLine(cmdInfo);
 cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
那么程序启动时,是不会调用CMDIChildWnd::OnUpdateFrameMenu的。这是因为,在创建主窗口时,已经把CFrameWnd::m_hMenuDefault,当作了Create的参数,成为主窗口默认菜单了,所有不需要调用CMDIChildWnd::OnUpdateFrameMenu。在LibUIDK中,由于CUIWnd不需要默认菜单,所以为了更新menu bar,应该调用CIUIMDIChildWnd::OnUpdateFrameMenu。或者CIUIMDIChildWnd::OnUpdateFrameMenu的简化代码:
  // destroying last child
  HMENU hMenuLast = NULL;
  ::SendMessage(pFrame->m_hWndMDIClient, WM_MDISETMENU,
   (WPARAM)pFrame->m_hMenuDefault, (LPARAM)hMenuLast);
由于显示MenuBar,必须在CMenuBar有效后进行,而CMenuBar,是在LibUIDK外部进行的。所以LibUIDK的做法是:
在CIUIMDIFrameWnd::OnCreate中,调用简化代码:
 HMENU hMenuLast = NULL;
 m_wndMDIClient.SendMessage(WM_MDISETMENU, (WPARAM)m_hMenuDefault, (LPARAM)hMenuLast);


////////////////////////////////////////////////////////////////////////////////
派生关系如下:

CFrameWnd
     CMDIFrameWnd
     CMDIChildWnd

阅读全文
0 0
原创粉丝点击