April 19, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Owner-Draw Menus with .NET and Managed C++

  • April 29, 2005
  • By Tom Archer
  • Send Email »
  • More Articles »

While my main forte is low-level programming, I've always enjoyed writing owner-draw and custom-draw controls. When you think about it, there's a lot of philosophical similarity between the two. When writing a device driver, you're writing at a level that is not very well documented and at first appears very foreboding. While not nearly as difficult, taking over the drawing for a control is much the same in terms of doing something that at first glance appears to be quite difficult when actually it's not—especially with .NET. This week's column illustrates just how easy it is to owner-draw a menu item using .NET and Managed C++.

First, it presents the steps for creating an owner-draw menu and shows how to implement those in your application. The second section then shows you how to use the simple, yet effective, owner-draw menu class that accompanies the article. That way, if you're in a hurry, you can simply plug the class into your application now and go back and read the code at your leisure.

Creating an Owner-Draw Menu

The following steps demonstrate how to create a File Exit menu using the Comic Sans MS font. As I dislike when an author assumes the state of my code, I'll start with creating the application. Obviously, if you're past that point, you can skip past the first couple of steps:
  1. Create a new Managed C++ Windows Forms application.
  2. Open the Visual Studio Toolbox window and drag a MainMenu control onto the project's form. (The default object name will be mainMenu1.)
  3. Double-click the form to create a Form1_Load method.
  4. Create a top-level menu by first instantiating a MenuItem object and then adding it to the form's MainMenu object:
    MenuItem* menuFile = new MenuItem(S"&File");
    mainMenu1->MenuItems->Add(menuFile);
    
  5. Create the submenu's MenuItem object and then add it to the File MenuItem object created in the previous step:
    MenuItem* menuExit = new MenuItem(S"E&xit");
    menuFile->MenuItems->Add(menuExit);
    
  6. Set the submenu's OwnerDraw property to true. Here, you're telling .NET that you're responsible for drawing the menu. That gives you complete control over its appearance. However, it also means that you will not be able to run the application and see the menu until you've implemented the drawing code.
  7. You need to implement two event handlers to handle owner-drawing a menu: MeasureItemEventHandler and DrawItemEventHandler. Implement the MeasureItemEventHandler first. The first step in this method is to retrieve the MenuItem object representing the item to be drawn (passed as the sender parameter). After that, the code instantiates a Font object and then determines the width and height needed to display the menu item's text using that font. Finally, the MeasureItemEventArgs object's width and height property are updated to reflect the size needed to display the item text in this font (.NET will use these values when rendering the menu item.):
    protected: void MenuMeasureItem(Object* sender, MeasureItemEventArgs* args)
    {
      // Retrieve the MenuItem object
      MenuItem* item = static_cast<MenuItem*>(sender);
    
      // Create the font object for the menu item
      System::Drawing::Font* font = 
        new System::Drawing::Font(S"Comic Sans MS", 16);
    
      // Retrieve the size of the menu item text
      SizeF siF = args->Graphics->MeasureString(item->Text, font);
    
      // Set the menu item's size based on the size needed for the text
      args->ItemWidth = Convert::ToInt32(Math::Ceiling(siF.Width));
      args->ItemHeight = Convert::ToInt32(Math::Ceiling(siF.Height));
    }
    
  8. Next, implement the MenuDrawItem event handler. After retrieving the MenuItem object and creating the Font object, the handler retrieves the size of the rectangle to be drawn (defining the menu item's coordinates) and then calls the DrawItemEventArgs::DrawBackground method. Next, the code determines if the user has currently highlighted the menu item. If so, it uses the system-defined "HighlightText" brush. Otherwise, it creates a brush using the system's defined menu-text color. Then it creates a StringFormat object that is used to pass additional formatting information to the Graphics::DrawString method. In this case, the handler is specifying that if the menu item has a mnemonic (the underlined character in a menu item represented by an ampersand), then the mnemonic needs to be displayed. Otherwise, the menu would display the ampersand character. Finally, the code calls the Graphics::DrawString to draw the menu item:
    protected: void MenuDrawItem(Object* sender, DrawItemEventArgs* args)
    {
      // Retrieve the MenuItem object
      MenuItem* item = static_cast<MenuItem*>(sender);
    
      // Create a Font object
      System::Drawing::Font* font = 
        new System::Drawing::Font(S"Comic Sans MS", 16);
    
      // Draw the background
      Rectangle recText = args->Bounds;
      args->DrawBackground();
    
      // Create a brush depending on if the user 
      // has selected the item
      Brush* brush;
      if ((args->State & DrawItemState::Selected) != 0) 
        brush = SystemBrushes::HighlightText;
      else 
        brush = SystemBrushes::FromSystemColor(SystemColors::MenuText);
    
      // Tell .NET that we want to display the mnemonic (underlined 
      // character in the menu text) if one is present
      StringFormat* format =  new StringFormat();
      format->HotkeyPrefix = 
        System::Drawing::Text::HotkeyPrefix::Show;
    
      // Draw the menu
      Graphics* g = args->Graphics;
      g->DrawString(item->Text, 
                    font,
                    brush,
                    Convert::ToDouble(recText.Left),
                    recText.Top,
                    format);
    }
    
  9. Return to the bottom of the Form1_Load method and add the MenuMeasureItem and MenuDrawItem event handlers created in the previous two steps to the submenu:
    menuExit->MeasureItem += 
      new MeasureItemEventHandler(this, MenuMeasureItem);
    
    menuExit->DrawItem += 
      new DrawItemEventHandler(this, MenuDrawItem);
    
  10. Add a "click" event handler for the menu item. Since this is an "exit" menu item, the code will simply close the current form:
    private: void OnExit(Object* sender, EventArgs* e) 
    {
      this->Close();
    }
    
  11. Normally, you would double-click the menu item in the Forms Designer to create a handler for the click event. However, since it is being created dynamically, you need to programmatically tell .NET which function will handle the "click" event. Therefore, return to the bottom of the Form1_Load method and add the following line of code:
    menuExit->Click += onClick;
    

If you build and run the application, you should see results similar to those shown in Figure 1.


Figure 1. Owner-Draw File Exit Menu





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel