【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
- 【LibUIDK界面库系列文章】MDI菜单机制
- 【LibUIDK界面库系列文章】代码风格
- 【LibUIDK界面库系列文章】空闲消息
- 【LibUIDK界面库系列文章】得到菜单的菜单窗口句柄
- 【LibUIDK界面库系列文章】窗口与消息
- 【LibUIDK界面库系列文章】倒序遍历vector
- 【LibUIDK界面库系列文章】goto语句的替代方案
- 【LibUIDK界面库系列文章】制作个性化桌面图标
- 【LibUIDK界面库系列文章】响应默认按钮
- 【LibUIDK界面库系列文章】通过指定模板定制CFontDialog
- 【LibUIDK界面库系列文章】Web Browser控件避免闪烁
- 【LibUIDK界面库系列文章】指定ComboBox的高度
- 【LibUIDK界面库系列文章】设置Edit控件的Margin
- 【LibUIDK界面库系列文章】打开和保存文件对话框
- 【LibUIDK界面库系列文章】RC控件语法
- 【LibUIDK界面库系列文章】对话框坐标单位
- 【LibUIDK界面库系列文章】解析网页中的table内容
- 【LibUIDK界面库系列文章】使用双窗口制作阴影边框时的激活问题
- 【LeetCode】寻找众数(绝对众数、1/k众数)
- 把python基本功搞扎实(4)
- Bitmap的使用习惯——及时释放Bitmap占用的内存
- POJ2942-Knights of the Round Table (双联通+判断奇环)
- C#之Socket断线重连
- 【LibUIDK界面库系列文章】MDI菜单机制
- Jzoj3883 线段树
- zookeeper搭建最简单
- [课程Project-物联网导论] 二维码的二值化 (1)
- 红黑树
- opencv学习笔记之5.4使用形态学滤波进行边缘和角点检测
- HDU1176免费馅饼
- java垃圾回收机制
- css3清除浮动