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

Although my main forté 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. Because 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 whether 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), 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. Because 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, because 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

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

Putting Everything into a Class

Now, condense everything into a more usable C++ class called ACG::MenuItem. Instead of redisplaying the same code already presented in the previous section, this section shows you only how to use the example class:

  1. Copy the ACG::MenuItem class from the demo project into your project.
  2. Insert the following code into your application's form-load method:
    // Instantiate the top level menu
    MenuItem* menuFile = new MenuItem(S"&File");
    mainMenu1->MenuItems->Add(menuFile);
    
    // Instantiate the owner-draw menu, specifying the
    // "click" event handler, font, and font's point size
    ACG::MenuItem* menuExit =
      new ACG::MenuItem(S"E&xit",
                        new EventHandler(this, ExitClick),
                        S"Comic Sans MS",
                        16);
    
    menuFile->MenuItems->Add(menuExit);
    
  3. Implement the "click" event handler:
    private: void ExitClick(Object* sender, EventArgs* e)
    {
      this->Close();
    }
    


About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

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

Top White Papers and Webcasts

  • The first phase of API management was about realizing the business value of APIs. This next wave of API management enables the hyper-connected enterprise to drive and scale their businesses as API models become more complex and sophisticated. Today, real world product launches begin with an API program and strategy in mind. This API-first approach to development will only continue to increase, driven by an increasingly interconnected web of devices, organizations, and people. To support this rapid growth, …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds