На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела FAQ в группе разделов С++.
1. Раздел FAQ предназначен для публикации готовых статей.
2. Здесь нельзя задавать вопросы, для этого существуют соответствующие разделы:
Чистый С++
Visual C++ / MFC / WTL / WinApi
Borland C++ Builder
COM / DCOM / ActiveX / ATL
Сопутствующие вопросы
3. Внимание, все темы и сообщения в разделе премодерируются. Любое сообщение или тема будут видны остальным участникам только после одобрения модератора.
Модераторы: B.V., Qraizer
  
> Owner-Drawn Menus step-by-step , заготовка в ФАК
    Часто можно увидеть в разных программах красивые менюшки, которые
    нельзя создать с помощью мастеров. Такие меню есть и в WordXP ExсelXP. Эта статья научит вас
    создавать такие меню.

    Такие меню не создаются автоматически - им надо рисовать себя самим. Итак:
    ШАГ 1. Чтобы пункт меню был саморисующийся ему надо установить стиль MF_OWNERDRAW. Поскольку оно само себя рисует - то надо создать обработчик DrawItem сообщения WM_DRAWITEM. Также мы должны сами определить размеры меню: надо создать обработчик MeasureItem сообщения WM_MEASUREITEM. И MF_OWNERDRAW и WM_DRAWITEM вызывается для _каждого_ пункта меню.
    Сделаем наше меню на основе MFC класса CMenu и назовём его CMenuEx. Оно будет простенькое, но при желании можно усложнить до требуемого состояния самому. Главное понять принципы, по которым оно работает.
    Значит у нас есть:
    ExpandedWrap disabled
      class CMenuEx : public CMenu  
      {
      public:
          virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
          virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
          CMenuEx() {};
          virtual ~CMenuEx() {};
      };
      void CMenuEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
      {}
      void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
      {}
    ШАГ 2. Допустим к нашему класу уже присоединён описатель стандартного меню. Нам надо для каждого пункта установить стиль MF_OWNERDRAW и ещё некоторые атрибуты (которые будут использоваться при отрисовке, или заданиии размеров). Для этого обьявим структуру:
    ExpandedWrap disabled
      struct MYOWNMENUITEM : public CObject
          {
              bool bIsTop;    // признак является ли пункт верхним в menu bar
              CString sCaption;   // надпись меню
       
              MYOWNMENUITEM()
              {
                  this->bIsTop = false;
                  this->sCaption = "";
              }
          };
    Также, для усложнения, в ней можно хранить значки, картинки либо признак какой-то особенности.
    Создадим метод, в котором пройдёмся по всем пунктам изменяя их стиль и заполняя их структуры.
    Пункты и субменю будем хранить в переменных класса
    ExpandedWrap disabled
          CPtrArray m_MenuArray;
          CPtrArray m_ItemArray;
    А вот наш метод:
    ExpandedWrap disabled
      void CMenuEx::Prepare(bool bTopLevel /*= false*/)
      {
          // bTopLevel - признак, что пункт меню есть верхним в menu bar
          for (UINT i=0; i < GetMenuItemCount(); i++)
          {
              MYOWNMENUITEM* pItem = new MYOWNMENUITEM;
       
              pItem->bIsTop = bTopLevel;
              GetMenuString(i, pItem->sCaption, MF_BYPOSITION);
              ModifyMenu(i, MF_BYPOSITION|MF_OWNERDRAW, GetMenuItemID(i), (TCHAR*) pItem);
              m_ItemArray.Add(pItem);
       
              if(GetSubMenu(i))
              {
                  CMenuEx* pMenu = new CMenuEx;
                  pMenu->m_pWnd = this->m_pWnd;
                  pMenu->Attach((this->GetSubMenu(i))->GetSafeHmenu());
                  m_MenuArray.Add(pMenu);
                  pMenu->Prepare();
              }
          };
      }

    Также для удобства я создал метод
    ExpandedWrap disabled
      void CMenuEx::MakeMenuEx(CWnd* pWnd, bool bToolBar/* = false*/)
      {
          m_pWnd = pWnd;
          if(bToolBar)
          {
              for(UINT i=0; i < GetMenuItemCount(); i++)
                  Prepare(true);
          }
          else
              Prepare();
      }
    Где переменная класса CWnd* m_pWnd - это окно, в котором показывается меню, а bToolBar - признак, является ли меню popup или toolbar.
    Соответственно, выделенную память нужно освободить в деструкторе:
    CMenuEx::~CMenuEx()
    ExpandedWrap disabled
      {
          for(INT32 a=0; a<m_MenuArray.GetSize(); a++)
          {
              delete (CMenuEx*) m_MenuArray[a];
          };
          for(INT32 b=0; b<m_ItemArray.GetSize(); b++)
          {
              delete (MYOWNMENUITEM*) m_ItemArray[b];
          };
      }

    ШАГ 3. ::Примечание:: id для сепаратора всегда =0, а для субменю =-1 !!!
    Надо определить размеры меню. Мы получаем LPMEASUREITEMSTRUCT - это указатель на
    структуру MEASUREITEMSTRUCT:
    ExpandedWrap disabled
      typedef struct tagMEASUREITEMSTRUCT {
          UINT CtlType;   // для меню всегда равно ODT_MENU
          UINT CtlID;     // не используется в меню
          UINT itemID;    // содержит ID пукта
          UINT itemWidth;     // ширина меню - нам надо установить желаемую ширину
          UINT itemHeight;    // высота меню - нам надо установить желаемую высоту
          DWORD itemData  // данные, которые добавлены к пункту с помощью методов CMenu::AppendMenu,
                  // CMenu::InsertMenu, CMenu::ModifyMenu
                  // Тут содержится наша структура MYOWNMENUITEM , которую мы добавляли в Prepare()
      } MEASUREITEMSTRUCT;
    Размер, для простоты, можно просто вбить:
    ExpandedWrap disabled
      void CMenuEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
      {
          lpMeasureItemStruct->itemHeight = 20;
          lpMeasureItemStruct->itemWidth = 50;
      }

    (в присоединённом проекте сделано чуть сложнее - там ширина зависит от длины надписи, от.....)

    Теперь рисуем пунк меню. Мы получаем LPDRAWITEMSTRUCT - это указатель на структуру DRAWITEMSTRUCT:
    ExpandedWrap disabled
      typedef struct tagDRAWITEMSTRUCT {
          UINT CtlType;   // для меню всегда равно ODT_MENU
          UINT CtlID;     // не используется в меню
          UINT itemID;    // содержит ID пукта
          UINT itemAction;    // Сообщает какое действие требуется отрисовать. Может содержать такие биты:
                  // ODA_DRAWENTIRE - этот бит установлен, когда пункт надо отрисовать
                  // ODA_FOCUS - этот бит установлен, когда пункт получает или теряет фокус.
                  // ODA_SELECT - этот бит установлен, когда пункт получает или теряет выделение
                  // [COLOR=blue]!!!Совет: Надо проверить itemState чтобы определить, когда пункт выделен.[/COLOR]
          UINT itemState;     // состояние пункта. Для меню может быть:
                  // ODS_CHECKED - установлен, когда пункт в состоянии checked
                  // ODS_DISABLED - установлен, когда пункт отключён
                  // ODS_FOCUS - установлен, когда пункт получает фокус
                  // ODS_GRAYED - установлен, когда пункт недоступный (dimmed, серый)
                  // ODS_SELECTED - установлен, когда пункт выбран
                  // ODS_DEFAULT - установлен, если пункт есть пунктом по умолчанию
          HWND hwndItem;  // определяет дескриптор меню (HMENU) которое содержит пункт меню
          HDC hDC;        // определяет контекст устройства, который используется для рисования пункта
          RECT rcItem;    // прямоугольник, который ограничивает наш пункт (его мы задавали в MeasureItem)
          DWORD itemData;     // данные, которые добавлены к пункту с помощью методов CMenu::AppendMenu,
                  // CMenu::InsertMenu, CMenu::ModifyMenu
                  // Тут содержится наша структура MYOWNMENUITEM , которую мы добавляли в Prepare()
      } DRAWITEMSTRUCT;
    Для простоты отрисовку можно сделать такую:
    ExpandedWrap disabled
      void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
      {
          // Получаем нашу структуру
          MYOWNMENUITEM* pItem = (MYOWNMENUITEM*)lpDrawItemStruct->itemData;
       
          CRect RFull(lpDrawItemStruct->rcItem); // Ограничивающий пункт прямоугольник
          // Зона значка, или в нашем случае - градиентной заливки
          CRect RIcon(RFull.left,RFull.top,RFull.left+m_szIconPadding.cx,RFull.top+RFull.bottom);
          // зона текста
          CRect RText(RIcon.right,RFull.top,RFull.right,RFull.bottom);
       
          COLORREF ColorIconRL = COLORREF(RGB(246,245,244)); // Цвет левой части заливки
          COLORREF ColorIconRR = COLORREF(RGB(0,209,201)); // Цвет правой части заливки
          COLORREF TextColor = COLORREF(RGB(249, 248, 247)); // Цвет фона текста
       
          if(pItem->bIsTop) // признак, что пункт меню есть верхним в menu bar
          {
              ZeroMemory(&RIcon, sizeof(CRect));
              RText = RFull;
              TextColor = GetSysColor(COLOR_BTNFACE);// COLORREF(RGB(192,192,192));
          }
       
          // получаем контекст, на котором будем рисовать
          CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
       
          // Функция градиентной заливки
          FillFluentRect(pDC->GetSafeHdc(), RIcon, 246,245,244,213,209,201);
       
          pDC->FillSolidRect(&RText, TextColor); // Рисуем фон текста
       
          pDC->SetBkColor(TextColor);
          
          // Рисуем текст пункта
          pDC->DrawText(pItem->sCaption, &RText, DT_EXPANDTABS|DT_LEFT|DT_VCENTER|DT_EDITCONTROL );
      }

    Но в нашем случае (во вложении) всё немного сложнее:
    ExpandedWrap disabled
      void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
      {
          //TRACE("CMenuEx::DrawItem\n");
       
          // Получаем нашу структуру
          MYOWNMENUITEM* pItem = (MYOWNMENUITEM*)lpDrawItemStruct->itemData;
       
              CRect RFull(lpDrawItemStruct->rcItem); // Ограничивающий пункт прямоугольник
          // Зона значка, или в нашем случае - градиентной заливки
          CRect RIcon(RFull.left,RFull.top,RFull.left+m_szIconPadding.cx,RFull.top+RFull.bottom);
          // зона текста
          CRect RText(RIcon.right,RFull.top,RFull.right,RFull.bottom);
       
          COLORREF ColorIconRL = COLORREF(RGB(246,245,244)); // Цвет левой части заливки
          COLORREF ColorIconRR = COLORREF(RGB(0,209,201)); // Цвет правой части заливки
          COLORREF TextColor = COLORREF(RGB(249, 248, 247)); // Цвет фона текста
       
          if(pItem->bIsTop) // признак, что пункт меню есть верхним в menu bar
          {
              ZeroMemory(&RIcon, sizeof(CRect));
              RText = RFull;
              TextColor = GetSysColor(COLOR_BTNFACE);// COLORREF(RGB(192,192,192));
          }
       
          // получаем контекст, на котором будем рисовать
          CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
       
          // Функция градиентной заливки
          FillFluentRect(pDC->GetSafeHdc(), RIcon, 246,245,244,213,209,201);
          // если есть значёк, то его можно отрисовать
          // поверх заливки с помощью функции BitBlt
       
          pDC->FillSolidRect(&RText, TextColor); // Рисуем фон текста
       
          if(lpDrawItemStruct->itemID == 0) // если этот пункт - это Separator
          {
              // Функция градиентной заливки
              FillFluentRect(pDC->GetSafeHdc(), RIcon, 246,245,244,213,209,201);
       
              pDC->FillSolidRect(&RText, TextColor); // рисуем фон
              CPen pen;
              pen.CreatePen(PS_SOLID, 1, GetSysColor(25));
              CPen* pOldPen = pDC->SelectObject(&pen);
       
              // рисуем сепаратор
              pDC->MoveTo(RText.left+5,  RText.top+(RText.bottom-RText.top)/2);
              pDC->LineTo(RText.right, RText.top+(RText.bottom-RText.top)/2);
       
              pDC->SelectObject(pOldPen);
              DeleteObject(pen);
          }
       
          else if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
                   (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)) )
          {
              // Если пункт выделен - то рисуем выделение
              if (!(lpDrawItemStruct->itemState & ODS_GRAYED)) // проверка доступен ли пункт
              {
                  TextColor = COLORREF(RGB(182, 189, 210));
                  pDC->FillSolidRect(&RFull, TextColor); // фон
                  CBrush* br = new CBrush;
                  br->CreateSolidBrush(COLORREF(RGB(10, 36, 106)));
                  pDC->FrameRect(&RFull, br); // рамка
                  delete br;
              };
          };
       
          if(lpDrawItemStruct->itemState & ODS_CHECKED) // если пункт в состоянии checked
          {
              // Checked Item
              HBITMAP     hBmp;
              CBitmap*    pBmp;
              BITMAP      bmp;
              CSize       szBmp;
              CPoint      ptBmp;
              ZeroMemory(&bmp, sizeof(BITMAP));
              
              // Загружаем значёк checked
              hBmp = ::LoadBitmap(NULL, MAKEINTRESOURCE(32760));
              pBmp = CBitmap::FromHandle(hBmp);
              pBmp->GetBitmap(&bmp);
              szBmp = CSize(bmp.bmWidth, bmp.bmHeight);
              ptBmp = CPoint(RIcon.left+(m_szIconPadding.cx-szBmp.cx)/2+1,
                              RIcon.top+(m_szIconPadding.cy-szBmp.cy)/2);
              // рисуем состояние
              pDC->DrawState(ptBmp, szBmp, hBmp, DSS_NORMAL|DSS_UNION);
              DeleteObject(hBmp);
          };
       
          // Устанавливаем цвет фона и границу надписи
          pDC->SetBkColor(TextColor);
          RText.left += m_szTextPadding.cx;
          RText.top += m_szTextPadding.cy;
          RText.bottom -= m_szTextPadding.cy;
       
          // если пункт недоступен - устанавливаем соответствующий цвет текста
          if (lpDrawItemStruct->itemState & ODS_GRAYED)
              pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
          // Рисуем текст пункта
          if(pItem->bIsTop)
              // если пункт меню есть верхним в menu bar - то выравниваем по центру
              pDC->DrawText(pItem->sCaption, &RText, DT_EXPANDTABS|DT_CENTER|DT_VCENTER);
          else
              // иначе - по левому краю
              pDC->DrawText(pItem->sCaption, &RText, DT_EXPANDTABS|DT_LEFT|DT_VCENTER|
                            DT_EDITCONTROL );
      }

    ШАГ 4. Использование:
    1) Если надо отобразить popup, то надо обьявить указатель CMenuEx* m_menu;
    В конструкторе окна создать обьект и инициализировать его:
    ExpandedWrap disabled
      CmenuView::CmenuView()
      {
          m_menu = new CMenuEx;
          m_menu->LoadMenuEx(IDR_MEMU, this);
      }
    соответственно в деструкторе - удалить обьект delete m_menu;
    Создать обработчик OnMeasureItem и вызвать из него MeasureItem нашего класса
    void CmenuView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    ExpandedWrap disabled
      {
          m_menu->MeasureItem(lpMeasureItemStruct);
       
          CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
      }

    Запустить popup при клике правой кнопкой мышки:
    void CmenuView::OnRButtonDown(UINT nFlags, CPoint point)
    ExpandedWrap disabled
      {
          ClientToScreen(&point);
          m_menu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, this);
       
          CView::OnRButtonDown(nFlags, point);
      }

    2) Если надо отобразить как menu bar, то тоже надо сначала обьявить указатель.
    Потом создать обьект в конструкторе и соответственно удаление в деструкторе.
    В OnCreate окна инициализировать и установить меню:
    ExpandedWrap disabled
      int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
      {
         ...........
          m_myme->LoadMenuEx(IDR_MAINFRAME, this, true);
          ::DestroyMenu(m_hMenuDefault);
          SetMenu(m_myme);
          m_hMenuDefault = m_myme->GetSafeHmenu();
         ............
      }
    Создать обработчики OnMeasureItem и OnDrawItem окна и вызвать из них соответствующие
    методы нашего меню:
    ExpandedWrap disabled
      void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
      {
          m_myme->MeasureItem(lpMeasureItemStruct);
       
          CFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
      }
       
      void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
      {
          m_myme->DrawItem(lpDrawItemStruct);
       
          CFrameWnd::OnDrawItem(nIDCtl, lpDrawItemStruct);
      }

    Вот и всё! :yes:

    Демонстрационное приложение.В прикрепленном архиве содержится проект, который демонстрируют работу с owner-drawn menu.

    Ссылки:Owner-Drawn Menu, не отрисовывается один пункт :(
    3 вопроса про меню
    http://www.codeproject.com/menu/
    http://www.codeproject.com/menu/owndraw.asp

    Список ключевых слов:
    CMenu, owner-drawn, MeasureItem, LPMEASUREITEMSTRUCT, MEASUREITEMSTRUCT, LPDRAWITEMSTRUCT, DRAWITEMSTRUCT, DrawItem, OnDrawItem, OnMeasureItem
    Прикреплённый файлПрикреплённый файлCopy_of_menu.rar (62.37 Кбайт, скачиваний: 282)
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script execution time: 0,0398 ]   [ 16 queries used ]   [ Generated: 25.04.24, 14:32 GMT ]