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

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • As more and more organizations migrate to the cloud, many are faced with hidden costs emerging from unexpected places. Two non-obvious and non-trivial factors can drive up costs. First are separate charges for everything from server memory to intrusion detection. Second are the high personnel costs for early-generation, manually operated clouds. These costs can rack up quickly, creating total cost of ownership (TCO) surprises. Keeping TCO low in the cloud is essentially a matter of management strategy. IT …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds