MFC OnMeasureItem 和 MDI multidoc 应用程序菜单中的 OnDrawItem



(更新,请参阅下面的原始问题)

经过一番挖掘,我基本上试图理解以下内容;在MDI应用程序的上下文中,如果菜单(与特定CChildWnd相关联)具有MF_OWNERDRAW,为什么ON_WM_MEASUREITEMON_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标志添加到每个必需的项目(在我的情况下为颜色选择项目)。

但是,为什么OnMeasureItemOnDrawItem事件发送到CMainWnd而不是CFooWnd?如何将事件定向到CFooWnd

我问的原因是,如果我的MDI应用程序中有5种不同类型的文档,每种都需要自定义菜单,那么CMainWnd基本上变成了一团糟的消息处理。自定义菜单逻辑的逻辑位置在CChildWnd,而不是CMainWnd

原始问题:

我正在一个非常旧的应用程序(MFC 4.2)上做一些工作,并且在绘制菜单项时遇到了问题。

原始应用程序有一个用于选择颜色的菜单,它实际上在打开时在菜单中绘制颜色,以便用户更容易选择颜色。

此行为在CMainWnd中使用OnMeasureItemOnDrawItem实现。

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_MEASUREITEMON_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 的指针,检查它是否处于活动状态,然后使用IsKindOfRUNTIME_CLASS检查类型,如果是,瞧,将行为委托给 ChildWnd。ToDoMeasureItemDoDrawItem只是在 ChildWnd 上实现的公共方法(有关详细信息,请参阅问题)。

最新更新