首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >MDI multidoc应用程序菜单中的MFC OnMeasureItem & OnDrawItem

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

Stack Overflow用户
提问于 2022-01-29 05:11:40
回答 1查看 145关注 0票数 1

(更新,见下面的原始问题)

在进行了一些深入研究之后,我基本上尝试理解以下内容:在MDI应用程序的上下文中,如果一个菜单(它与特定的CChildWnd相关联)有一个MF_OWNERDRAW,为什么ON_WM_MEASUREITEMON_WM_DRAWITEM事件发送到CMainWnd而不是CChildWnd

在我的InitInstance中,文档模板被注册,关联的菜单被修改以添加MF_OWNERDRAW

代码语言:javascript
复制
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中。

原始问题:

我在一个非常老的应用程序(MF4.2)上做了一些工作,在菜单项中绘制遇到了问题。

原始应用程序有一个菜单来选择颜色,当打开菜单时,它实际上会绘制菜单中的颜色,这样用户就更容易选择颜色。

CMainWnd中使用OnMeasureItemOnDrawItem实现的行为。

代码语言:javascript
复制
class CMainWnd : public CMDIFrameWnd
{
    DECLARE_DYNCREATE(CMainWnd)
    
protected:
    afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
    afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
    
    DECLARE_MESSAGE_MAP()
};

然后,在实现中(为了简洁起见省略了一些部分):

代码语言:javascript
复制
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文档的框架类中,而不是让它位于应用程序主框架中?

EN

回答 1

Stack Overflow用户

发布于 2022-02-02 10:11:58

我想出了个办法。并不理想,但我可以理解这是框架中的一个怪癖,即菜单似乎是MainWnd的一部分,因此从技术角度来看,也就是处理ON_WM_MEASUREITEMON_WM_DRAWITEM的地方。

不管怎么说,我的工作。基本上捕获MainWnd中的事件,然后将行为委托给ChildWnd。这里的诀窍(我猜)是找出要委派给什么ChildWnd,因为在MDI应用程序中可以有任意数量的不同的ChildWnd(每个有自己的文档和视图类型)。

这方面的工作:

代码语言:javascript
复制
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。To DoMeasureItemDoDrawItem只是在ChildWnd上实现的公共方法(详见问题)。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70902481

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档