Owner Drawing the Submenu Arrow

I was looking for info on how to do some custom drawing with respect to the submenu arrow recently, only to find that each question about it went unanswered. So, because I have solved the problem much to my liking, I would like to share the solution with whomever might find it useful. My goal was to simply make disabled custom drawn submenus have disabled looking submenu arrows, but there seemed to be no obvious way to do this. In finding a solution, I also found a way to simply do custom submenu drawing if that is what you want to do also.

First, start with the owner draw handling code:

LRESULT CMyMenuClass::MyDrawMenuItem(MSG &iMsg)
{
   LRESULT result = 0;

   DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT*)iMsg.lParam;
   HDC hDC = dis->hDC;

   if(dis->CtlType == ODT_MENU)
   {
      CMyMenuItem *menuItem = (CMyMenuItem*)dis->itemData;
      RECT textR            = dis->rcItem;
      bool isSubmenu        = menuItem->IsSubmenu();
      bool isEnabled        = menuItem->IsEnabled();

      //Draw custom submenu arrow
      if(isSubmenu)
      {
         HBITMAP hBitmap = (HBITMAP)::LoadImage(NULL,
            MAKEINTRESOURCE(OBM_MNARROW), IMAGE_BITMAP, 0, 0,
            LR_DEFAULTSIZE | LR_SHARED);
         BITMAP bm;
         ::GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);
         RECT arrowR  = textR;
         textR.right -= bm2.bmWidth;
         arrowR.left  = textR.right;
         int arrowW   = (arrowR.right - arrowR.left);
         int arrowH   = (arrowR.bottom - arrowR.top);
         ::DeleteObject(hBitmap);

         //Center the arrow rect vertically
         if(arrowH > bm2.bmHeight)
         {
            int offsetH   = (bm2.bmHeight - arrowH) / 2;
            arrowR.top   += offsetH;
            arrowR.bottom = arrowR.top + bm2.bmHeight;
         }

         //Draw the arrow
         this->MyDrawMenuArrow(hDC, arrowR, isEnabled);
      }

      //Do your custom menu drawing here
      this->MyDrawMenuItem(menuItem, textR);
   }

   RECT tmpR = dis->rcItem;
   ::ExcludeClipRect(hDC, tmpR.left, tmpR.top, tmpR.right,
                     tmpR.bottom);
}

So in the function above, start with the last line of code first. This call basically is what stops the OS from drawing the submenu item for you, so now you will have a "clear" palette to work with. In the section pertaining to the submenu drawing, I retrieve the submenu and enabled flags that I stored with each item during menu creation in the item's data (code not shown). If the item is a submenu, I calculate the proper arrow rect (taking note to shorten the text rect also so there will be no overlap), and then pass that rect, along with the output HDC and the enabled state, into my draw arrow function.

In the draw arrow function, you could do anything you want to customize the submenu arrows, so if you wanted to do something non-OS looking, here is where you do that. I just wanted to have an OS look, but specific functionality that is, for some reason, not available.

Here is my draw arrow function:

void CMyMenuClass::MyDrawMenuArrow(HDC inHDC, RECT &inDestR,
                                   bool inIsEnabled)
{
   //Create the DCs and Bitmaps we will need
   HDC arrowDC = ::CreateCompatibleDC(inHDC);
   HDC fillDC  = ::CreateCompatibleDC(inHDC);
   int arrowW  = inDestR.right - inDestR.left;
   int arrowH  = inDestR.bottom - inDestR.top;
   HBITMAP arrowBitmap    = CreateDIBBitmap(inHDC, arrowW, arrowH);
   HBITMAP oldArrowBitmap = (HBITMAP)::SelectObject(arrowDC, arrowBitmap);
   HBITMAP fillBitmap     = CreateDIBBitmap(inHDC, arrowW, arrowH);
   HBITMAP oldFillBitmap  = (HBITMAP)::SelectObject(fillDC, fillBitmap);

   //Set the offscreen arrow rect
   RECT tmpArrowR;
   ::SetRect(&tmpArrowR, 0, 0, arrowW, arrowH);

   //Draw the frame control arrow (The OS draws this as a black on
   //                              white bitmap mask)
   ::DrawFrameControl(arrowDC, &tmpArrowR, DFC_MENU, DFCS_MENUARROW);

   //Set the arrow color
   HBRUSH arrowBrush = inIsEnabled ? ::GetSysColorBrush(COLOR_MENUTEXT) :
                                     ::GetSysColorBrush(COLOR_GRAYTEXT);

   //Fill the fill bitmap with the arrow color
   ::FillRect(fillDC, &tmpArrowR, arrowBrush);

   //Blit the items in a masking fashion
   ::BitBlt(inHDC, inDestR.left, inDestR.top, arrowW, arrowH, fillDC,
            0, 0, SRCINVERT);
   ::BitBlt(inHDC, inDestR.left, inDestR.top, arrowW, arrowH, arrowDC,
            0, 0, SRCAND);
   ::BitBlt(inHDC, inDestR.left, inDestR.top, arrowW, arrowH, fillDC,
            0, 0, SRCINVERT);

   //Clean up
   ::SelectObject(fillDC, oldFillBitmap);
   ::DeleteObject(fillBitmap);
   ::SelectObject(arrowDC, oldArrowBitmap);
   ::DeleteObject(arrowBitmap);
   ::DeleteDC(fillDC);
   ::DeleteDC(arrowDC);
}

In the function above, CreateDIBBitmap is just a utility function I wrote; it's used to create a compatible DIB bitmap. You can use whatever method you prefer to create your bitmap.

I decided that, because I just wanted a simple arrow, I would stick to using DrawFrameControl. For me, making sure that I could use the disabled color was the most important thing, and to my dismay, state flags have no effect on how the submenu item is drawn. Basically, the documentation states that it will just create a black on white mask for you, and that is it. If I wanted to take it to the next level, I might check for a theme, and use theme drawing if applicable.

Here is what the disabled submenu arrows look like afterwards:



About the Author

David Sumich

When the sin smiles, how can it be wrong?

Comments

  • regarding submenus

    Posted by jyothi on 06/01/2014 10:02am

    Hai sir,I wanted to change sub menu arrow mark color please tell me how

    Reply
  • THX

    Posted by Sebastian on 12/06/2012 06:45am

    ExcludeClipRect, maybe just a workaround but it works, thx for sharing information !!!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • On-demand Event Event Date: September 17, 2014 Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this webcast and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the future state of your servers, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds