(更新,见下面的原始问题)
在进行了一些深入研究之后,我基本上尝试理解以下内容:在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中。
原始问题:
我在一个非常老的应用程序(MF4.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文档的框架类中,而不是让它位于应用程序主框架中?
发布于 2022-02-02 10:11:58
我想出了个办法。并不理想,但我可以理解这是框架中的一个怪癖,即菜单似乎是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。To DoMeasureItem和DoDrawItem只是在ChildWnd上实现的公共方法(详见问题)。
https://stackoverflow.com/questions/70902481
复制相似问题