MFC自绘菜单

时间:2023-03-09 02:34:26
MFC自绘菜单

自绘控件问题多多。本文以菜单为例。

①当要使用顶层菜单资源、对话框资源、状态栏资源等这3种资源的任何一种。那么CWinApp::InitInstance函数内部必须使用LoadFrame函数来加载资源。比如以下代码:
BOOL CMyApp::InitInstance()
{
  CMainWindow* pFrame = new CMainWindow;
  m_pMainWnd = pFrame;

  pFrame->LoadFrame(IDR_MAINFRAME);
  pFrame->ShowWindow(m_nCmdShow);
  pFrame->UpdateWindow();

  return TRUE;
}

以下代码适合使用框架窗口但不使用以上三种资源时候使用。
BOOL CMyApp::InitInstance()
{
  m_pMainWnd = new CMainWindow;
  m_pMainWnd->ShowWindow(m_nCmdShow);
  m_pMainWnd->UpdateWindow();

  return TRUE;
}

如果第2种代码被使用在顶层菜单资源、对话框资源、状态栏资源等这3种资源的任何一种,那么可能会有不可预知的错误发生。

②在OnCreate函数里面应该使用以下代码:

int CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if(CFrameWnd::OnCreate(lpCreateStruct) == -1)
    return -1;

  m_wndToolBar.Create(this);
  m_wndToolBar.LoadToolBar(IDR_MAINFRAME);

  UINT nIndicator = ID_SEPARATOR;
  m_wndStatusBar.Create(this);
  m_wndStatusBar.SetIndicators(&nIndicator,1);

  CMenu* pMenu = GetMenu()->GetSubMenu(2);
  pMenu->ModifyMenu(ID_VIEW_STATUS_BAR,MF_OWNERDRAW,ID_VIEW_STATUS_BAR);
  return 0;
}

以上代码里面没有加载菜单的语句,这是在CFrameWnd::LoadFrame函数里面已经被MFC自动加载了。但是工具栏却没有,必须使用CToolBar::LoadToolBar函数加载。状态栏无需加载。

③自绘控件的关键在于CFrameWnd::OnMeasureItem函数和CFrameWnd::OnDrawItem函数。必须在CFrameWnd的派生类的消息映射项里面使用ON_WM_MEASUREITEM()和ON_WM_DRAWITEM()才能使用这2个函数。同时也头文件必须在类声明的最后使用以下代码:
afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis);
afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis);

OnMeasureItem函数比较简单。在每个自绘菜单项第1次显示之前被框架调用,以确定大小。OnDrawItem函数在每个自绘菜单项第2次以及更多次显示之前被调用。目的在于根据第2个参数lpmis具体绘制菜单项内容。在函数中,一般根据以下代码原型绘制菜单。
void CMainWindow::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
  if(lpdis->CtlType == ODT_MENU)
  {
    //别的代码
    CDC dc;
    dc.Attach(lpdis->hDC);
    //dc对象的绘制函数
    dc.Detach();
    //别的代码
  }
}

lpdis指向的结构如下:

typedef struct tagDRAWITEMSTRUCT {
    UINT    CtlType;
    UINT    CtlID;
    UINT    itemID;
    UINT    itemAction;
    UINT    itemState;
    HWND   hwndItem;
    HDC     hDC;
    RECT    rcItem;
    DWORD  itemData;
} DRAWITEMSTRUCT;

CtlType成员可以参考MSDN里面的资料。总共有7种自绘控件类型。
CtlID成员和itemData成员跟菜单无关。
itemID成员就是将要绘制的具体菜单项的ID。
itemAction成员可以是ODA_DRAWENTIRE、ODA_FOCUS、ODA_SELECT的任意组合。ODA_DRAWENTIRE的意思是画出整个菜单项。ODA_FOCUS表示焦点有变化。失去或者获得焦点。ODA_SELECT表示当前菜单项加亮显示的情况。
当itemAction包含ODA_FOCUS且itemState 包含ODS_FOCUS,那么表示获得焦点。
当itemAction包含ODA_FOCUS但itemState 不含ODS_FOCUS,那么表示失去焦点。
当itemAction包含ODA_SELECT且itemState 包含ODS_SELECTED,那么表示加量显示。
当itemAction包含ODA_SELECT但itemState 不含ODS_SELECTED,那么表示正常显示。
itemState成员总共有7个标志。具体参考MSDN里面的资料。
hDC是菜单项所在的DC句柄。
rcItem是限制的可被绘画的矩形。

以上OnMeasureItem函数和OnDrawItem函数的第1个参数int nIDCtl跟自绘菜单无关。只跟非菜单自绘控件有关,是它们的控件ID。

以上信息可以从可以参考《MFC Windows程序设计》的第2版(修订版)里面的第200页。

④自绘控件不是以上内容那么简单,里面有很多不该有的bug。根据提供的代码绘制出下图:
MFC自绘菜单
上图是代码运行后得到的结果图。里面告诉你很多MFC本身的bug。

①顶层菜单里面的“查看”下面的下拉菜单第2个是自绘菜单。绘图时候覆盖了上面的“工具栏”的菜单项。这明显是错误的。
②菜单项的北京颜色默认是白色,但是画出来却是黑色。这也是错误的。这个错误不是固定的,只要把光标移动到自绘菜单项,背景就会变成白色的。再移动到“工具栏”项。背景还是白色的,是正确的。
③从下图可知,Top:18是指“工具栏”下面的那像素行所在的垂直坐标。其实应该是把0,0设置在“工具栏”左下角下面的那个像素才是正常的。
MFC自绘菜单
④以下代码设置了绘图区域的大小。表示高度的lpmis->itemHeight为100。实际是上图黑色部分的高度(Bottom-Top=100)。不含“工具栏”本身的高度。可见这是正确的。但是宽度就是有问题的。Right-Left=131,而不是lpmis->itemWidth 的100。里面多出来的有15像素是为最前面的“单选”的“黑点”标记或“复选”的“打勾”标记。再来15像素是自定义“图像”。最后1像素是多出来的。

void CMainWindow::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
  lpmis->itemHeight = 100;
  lpmis->itemWidth = 100;
}

⑤::GetSystemMetrics(SM_CYMENU)函数返回值是20,这是不对的。应该是18才对。