(更新,请参阅下面的原始问题)
经过一番挖掘,我基本上试图理解以下内容;在MDI应用程序的上下文中,如果菜单(与特定CChildWnd
相关联)具有MF_OWNERDRAW
,为什么ON_WM_MEASUREITEM
和ON_WM_DRAWITEM
事件发送到CMainWnd
而不是CChildWnd
?
在我的InitInstance
中,文档模板已注册,并修改了关联的菜单以添加MF_OWNERDRAW
:
BOOL CMyApp::InitInstance()
{
// ...
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_CHILDFRAME,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CFooWnd),
RUNTIME_CLASS(CFooView)
);
if (pDocTemplate->m_hMenuShared != NULL) {
CMenu* pMenu = CMenu::FromHandle(pDocTemplate->m_hMenuShared);
// Add MF_ONWERDRAW to the items that need it.
pMenu->ModifyMenu([item_id], MF_BYCOMMAND | MF_OWNERDRAW, [item_id]);
}
AddDocTemplate(pDocTemplate);
// ...
}
因此,注册文档模板后,将修改与文档/框架关联的菜单,以将MF_ONWERDRAW
标志添加到每个必需的项目(在我的情况下为颜色选择项目)。
但是,为什么OnMeasureItem
和OnDrawItem
事件发送到CMainWnd
而不是CFooWnd
?如何将事件定向到CFooWnd
?
我问的原因是,如果我的MDI应用程序中有5种不同类型的文档,每种都需要自定义菜单,那么CMainWnd
基本上变成了一团糟的消息处理。自定义菜单逻辑的逻辑位置在CChildWnd
,而不是CMainWnd
。
原始问题:
我正在一个非常旧的应用程序(MFC 4.2)上做一些工作,并且在绘制菜单项时遇到了问题。
原始应用程序有一个用于选择颜色的菜单,它实际上在打开时在菜单中绘制颜色,以便用户更容易选择颜色。
此行为在CMainWnd
中使用OnMeasureItem
和OnDrawItem
实现。
class CMainWnd : public CMDIFrameWnd
{
DECLARE_DYNCREATE(CMainWnd)
protected:
afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
DECLARE_MESSAGE_MAP()
};
然后,在实现中(为简洁起见,省略了零碎的内容):
BEGIN_MESSAGE_MAP(CMainWnd, CMDIFrameWnd)
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemWidth = ::GetSystemMetrics(SM_CYMENU) * 4;
lpmis->itemHeight = ::GetSystemMetrics(SM_CYMENU) * 1;
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CBrush* pBrush;
// draw the hover/selection rectangle
pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT :
COLOR_MENU));
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
// load a checkbox icon into a bitmap
BITMAP bm;
CBitmap bitmap;
bitmap.LoadOEMBitmap(OBM_CHECK);
bitmap.GetObject(sizeof(bm), &bm);
// if color/item selected then draw the checkbox
if (lpdis->itemState & ODS_CHECKED) {
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
dc.BitBlt(
lpdis->rcItem.left + 4,
lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / bm.bmWidth,
bm.bmHeight,
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(pOldBitmap);
}
// draw the actual color bar
pBrush = new CBrush(CPaintDoc::m_crColors[lpdis->itemID - ID_COLOR_BLACK]);
CRect rect = lpdis->rcItem;
rect.DeflateRect(6, 4);
rect.left += bm.bmWidth;
dc.FillRect(rect, pBrush);
delete pBrush;
dc.Detach();
}
OnDrawItem
的作用是;它绘制一个带有颜色的水平颜色条,如果选择了该颜色,并且悬停在菜单项上由在其周围绘制的框突出显示,则以复选图标为前缀。
但是,由于我正在将此应用程序转换为 Multidoc 应用程序,并且我真的不觉得此逻辑应该在CMainWnd
中(因为其他文档都没有这种类型的菜单),但它应该是CChildWnd
的一部分(继承自CMDIChildWnd
)。
但是,当我将此逻辑移动到该类时,当我运行应用程序时,我在控制台记录器中收到以下消息:
警告:菜单项
0x0082
的未知WM_MEASUREITEM
。
而且自定义菜单行为似乎都不起作用。
所以,问题是;如何将菜单的自定义行为移动到 MDI 文档的框架类中,而不是将其位于应用程序主框架中?
我想出了一个解决方法。不理想,但我可以理解这是框架中的一个怪癖,即菜单似乎是 MainWnd 的一部分,所以从技术角度来看,这就是处理ON_WM_MEASUREITEM
和ON_WM_DRAWITEM
的地方。
无论如何,我的工作。基本上捕获 MainWnd 中的事件,然后将行为委托给 ChildWnd。这里的诀窍(我猜)是弄清楚要委派给哪个 ChildWnd,因为在 MDI 应用程序中可以有任意数量的不同 ChildWnd(每个都有自己的文档和视图类型)。
解决方法:
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoMeasureItem(nIDCtl, lpmis);
}
}
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoDrawItem(nIDCtl, lpdis);
}
}
}
非常简单,在 MainWnd 的上下文中,获取指向活动 MDI ChildWnd 的指针,检查它是否处于活动状态,然后使用IsKindOf
和RUNTIME_CLASS
检查类型,如果是,瞧,将行为委托给 ChildWnd。ToDoMeasureItem
和DoDrawItem
只是在 ChildWnd 上实现的公共方法(有关详细信息,请参阅问题)。