Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Flat-style Menu Bar and Popup Menu Control for WinForms

4.79/5 (47 votes)
1 Feb 2006CPOL6 min read 1   4.8K  
A custom control for adding flat-style menu bars, navigation bars, or popup menus to your WinForms application

Image 1

Introduction

This article presents a flat-style menu control which can be used to implement menu bars, simple navigation bars, or popup menus that can appear anywhere on your form. The FlatMenuBar control provides support for menu bars while the FlatPopupMenu control can be used to display popup (or context) menus. My objective with these controls was to have a visually clean menu appearance and simpler UI navigation model, much like the menus that you see on some web sites. I also wanted to support certain effects such as gradient color backgrounds for popup menus. But my intent is not for this control to replace or act as a substitute for the existing Win Forms menu functionality in the .NET class framework. For example, some standard menu features such as keyboard navigation are not currently supported.

Menu Framework

FlatMenuBar and FlatPopupMenu are custom controls that are based on a small framework of three main classes which I've defined in the source file, FlatMenu.cs, under the MenuControls namespace. These menu framework classes are described as follows.

FlatMenuItemList is essentially a container for a set of menu items which appear together on the same menu bar or popup menu. The public interface of this class is shown below for reference:

C#
public class FlatMenuItemList
{
    // Properties.
    public int Count {...}
    
    // Constructor.
    public FlatMenuItemList() {...}
    
    // Manage menu items.
    public bool         Add(FlatMenuItem item) {...}
    public FlatMenuItem Add(string text,
                            EventHandler clickHandler) {...}
    public bool         AddSeparator() {...}
    public void         Clear() {...}
    public FlatMenuItem GetAt(int index) {...}
    public bool         Remove(FlatMenuItem item) {...}
    public bool         RemoveAt(int index) {...}
    
    // Output menu item Text values
    // into a single string.
    public override string ToString() {...}
}

FlatMenuItem encapsulates the information for a single menu item. Implemented using FlatMenuItemList, it can also contain child menu items (to create a sub-menu that extends off that menu item). The public interface of this class is shown below for reference:

C#
public class FlatMenuItem
{    
    // Events.
    public event System.EventHandler Click;
    
    // Properties.
    public bool              Checked {...}
    public Rectangle         ClientRectangle {...}    
    public bool              Enabled {...}  
    public bool              HasClickHandler {...}  
    public FlatMenuItemList  MenuItems {...}
    public bool              Radio {...}
    public bool              Selectable {...}   
    public FlatMenuItemStyle Style {...}
    public string            Text {...}    
   
    // Constructor.
    public FlatMenuItem() {...}
        
    // Event handling.
    public void NotifyClickEvent() {...}
    
    // Output the Text value into a string.
    public override string ToString() {...}
}

FlatMenu is an abstract class that is also the base class for FlatMenuBar and FlatPopupMenu. This is the key component of the framework. FlatMenu declares four abstract methods which the derived classes must implement in order to provide specific functionality for positioning menu item rectangles, drawing menus, positioning popup menus, and showing popup menus. Although FlatMenu is abstract, it actually implements most of the menu navigation logic, leaving just the drawing (visual appearance) and positioning details to be provided by the derived classes. The public members and the abstract methods of this class are shown below for reference:

C#
public abstract class FlatMenu : System.Windows.Forms.Control
{
    // Events.
    public event CurrentMenuItemChangeEventHandler
                     CurrentMenuItemChange;

    // Menu ID property, useful for debugging
    // or identifying menu instances.
    public int MenuId {...}
    
    // Color properties for normal menu item state.
    public GradientColor BackGradientColor {...}
    public Color         BorderColor {...}
    public Color         SeparatorColor {...}
    public Color         TextColor {...}

    // Color properties for menu item hover state.
    public GradientColor HoverBackGradientColor {...}
    public Color         HoverBorderColor {...}
    public Color         HoverTextColor {...}
    
    // Disabled menu item text color.
    public Color DisabledTextColor {...}
    
    // Text font for menu item hover state.
    public Font HoverFont {...}
    
    // Options for border and background drawing.
    public bool EnableBorderDrawing {...}
    public bool EnableHoverBorderDrawing {...}
    public bool EnableHoverBackDrawing {...}
    
    // Offset of first menu item text rectangle
    // from top-left corner of control.
    public int LeftMargin {...}
    public int TopMargin {...}
    
    // Horizontal or vertical spacing between
    // menu items.
    public int MenuItemSpacing {...}
    
    // Timer interval for auto-closing menu.
    public int MenuTimerInterval {...}
    
    // Is this a popup-style menu?
    public bool IsPopupMenu {...}
    
    // Access the popup menu.
    public FlatMenu Popup {...}
    
    // Parent menu and parent menu item.
    public FlatMenu     ParentMenu {...}
    public FlatMenuItem ParentMenuItem {...}
    
    // List of child menu items.
    public FlatMenuItemList MenuItems {...}
    
    // Constructor.
    public FlatMenu() {...}
    
    // Output the menu ID into a string.
    public override string ToString() {...}
    
    // Abstract methods.
    protected abstract void DrawMenu(Graphics g);
    protected abstract void 
        RepositionMenuItems(Graphics g);
    protected abstract void CreatePopupMenu();
    protected abstract void
        GetPopupMenuScreenLocation(ref Point ptScreen);
}

Using FlatMenuBar

The FlatMenuBar and FlatPopupMenu control classes are defined in the source file, FlatMenuBar.cs. As mentioned earlier, they are both derived from FlatMenu. An interesting thing to note is that FlatMenuBar uses FlatPopupMenu itself in order to implement cascading submenus.

Once the source code has been compiled into a DLL or EXE, you can open your form in the designer and select Tools | Add/Remove Toolbox Items in order to add the FlatMenuBar control to your VS toolbox. Once added, you can drag a FlatMenuBar instance to your form - it will appear simply as a navy blue rectangle with grey text which you can reposition and resize as desired. Visual Studio will give the control variable a default name such as flatMenuBar1.

Once the menu bar instance is created, we can begin adding menu items. I typically write a single private method that does all of the setup and initialization. Then, I call this method from the form's constructor. For example, to create a top-level File menu item, we can write the LoadMenuBar1() method shown below:

C#
public class MainForm : System.Windows.Forms.Form
{
    ...

    public MainForm()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        
        // Populate menu bar 1.
        LoadMenuBar1();
    }
    
    private void LoadMenuBar1()
    {
        // File menu item.
        FlatMenuItem fileItem = new FlatMenuItem();
        fileItem.Text = "File";
        this.flatMenuBar1.MenuItems.Add(fileItem);
    }
}

Now, suppose we want to have a File menu, containing menu items such as New and Exit, along with a separator item between the two. The following code illustrates how to do this:

C#
private void LoadMenuBar1()
{
    ...
    
    // File | New menu item.
    FlatMenuItem newItem = new FlatMenuItem();
    newItem.Text = "New";
    fileItem.MenuItems.Add(newItem);

    // Add separator item after New.
    fileItem.MenuItems.AddSeparator();

    // File | Exit menu item.
    FlatMenuItem exitItem = new FlatMenuItem();
    exitItem.Text = "Exit";
    fileItem.MenuItems.Add(exitItem);    
}

Next, if we want to have the New menu item extend to a further submenu, containing the menu item, Window, we can add the following code:

C#
private void LoadMenuBar1()
{
    ...
    
    // File | New | Window menu item.
    FlatMenuItem windowItem = new FlatMenuItem();
    windowItem.Text = "Window";
    newItem.MenuItems.Add(windowItem);
}

The resultant menu bar should look as follows when the File menu is opened:

Image 2

Event Handling

Besides just displaying a menu item, we also want to add a callback function that is invoked when the user selects a menu item. To do this, we can add an event handler for a menu item's Click event:

C#
public class MainForm : System.Windows.Forms.Form
{
    ...

    private void LoadMenuBar1()
    {
        ...
        windowItem.Text = "Window";
        windowItem.Click +=
            new System.EventHandler(windowItem_Click);
        ...
    }

    private void windowItem_Click(object obj,
                                  System.EventArgs e)
    {
        MessageBox.Show("New Window Clicked");
    }
}

Note that the Click event handler for a menu item will only take effect if the menu item does not have any children of its own, and the menu item is enabled. For example, if you disable a menu item using the code as shown below, the Click handler will be ignored (actually, the menu item won't even be selectable):

C#
private void LoadMenuBar1()
{
    ...
    windowItem.Enabled = false;
    ...
}

There is another event which the client code can receive from the menu itself (FlatMenuBar or FlatPopupMenu instance). This is the CurrentMenuItemChange event and it is fired by the menu as the user navigates through the menu items. This event can be used to display a description about the currently highlighted menu item, for example:

C#
public class MainForm : System.Windows.Forms.Form
{
    ...

    private void LoadMenuBar1()
    {
        ...
    
        // Add an event handler.
        this.flatMenuBar1.CurrentMenuItemChange +=
            new CurrentMenuItemChangeEventHandler(
                  flatMenuBar1_CurrentMenuItemChange);
    }

    private void flatMenuBar1_CurrentMenuItemChange(
                     object obj,
                     CurrentMenuItemChangeEventArgs e)
    {      
        // Display the name of the currently
        // highlighted menu item.
        FlatMenuItem item = e.CurrentMenuItem;
        if ( item == null )
            this.currItemLabel.Text = "None";
        else
            this.currItemLabel.Text = item.Text;
    }
}

Using FlatPopupMenu

A FlatPopupMenu instance can be loaded with menu items in the same manner as with FlatMenuBar. The only difference is that it is not necessary to drag an instance of the control first to your form. An instance can be created on demand, initialized, and then shown as needed (e.g., on a context menu right-click). A FlatPopupMenu instance can be displayed by invoking its TrackPopupMenu() method, of which there are two overloaded versions:

C#
public class FlatPopupMenu : MenuControls.FlatMenu
{
    ...
        
    public void TrackPopupMenu(Control parent,
                    FlatMenuAlignment alignX,
                    FlatMenuAlignment alignY,
                    int x, int y) {...}
        
    public void TrackPopupMenu(Control parent, 
                               int x, int y) {...}
}

Below is a sample code for showing a popup menu on a mouse right-click over a form:

C#
public class MainForm : System.Windows.Forms.Form
{
    ...
    
    private FlatPopupMenu popupMenu = null;
    
    protected override void Dispose( bool disposing )
    {
        if ( disposing )
        {
            // Dispose managed resources.
            ...           
            if ( this.popupMenu != null )
            {
                this.popupMenu.Dispose();
                this.popupMenu = null;
            }
        }
        base.Dispose( disposing );
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);

        // Only process mouse right-clicks.
        if ( e.Button != MouseButtons.Right )
            return;

        ShowPopupMenu(e.X, e.Y);
    }
    
    private void ShowPopupMenu(int x, int y)
    {
        if ( this.popupMenu == null )
        {
            // Create the popup menu.
            this.popupMenu = new FlatPopupMenu();
                
            // Set color properties.
            this.popupMenu.BackColor = Color.Red;
                
            // Add menu items.
            FlatMenuItem popupItem1 = new FlatMenuItem();
            popupItem1.Text = "Popup Item 1";
            FlatMenuItem popupItem2 = new FlatMenuItem();
            popupItem2.Text = "Popup Item 2";
            FlatMenuItem popupItem3 = new FlatMenuItem();
            popupItem3.Text = "Popup Item 3";                
            this.popupMenu.MenuItems.Add(popupItem1);
            this.popupMenu.MenuItems.Add(popupItem2);
            this.popupMenu.MenuItems.Add(popupItem3);
        }
            
        // Display the popup menu at the given
        // mouse location.
        this.popupMenu.TrackPopupMenu(this, x, y);
    }    
}

The TestFlatMenu Application

The demo project (TestFlatMenu) is a simple WinForms application which displays five different kinds of menu bars plus one popup menu (context menu). Instead of having menu item Click handlers that just display a message box, I decided to do something more interesting and have the Click event trigger the opening of a web page in an embedded Microsoft Web Browser control.

Note: To add this control to your own VS toolbox, just use Tools | Add/Remove Toolbox Items, select the COM components tab, and then look for the Microsoft Web Browser control. Below is a snapshot of the application that shows a FlatPopupMenu instance being opened on a mouse right-click over the form:

Image 3

Summary

The menu control presented in this article is more appropriate I think for creating navigation bars with web-like UI behavior. That was my initial goal - I didn't really intend for the control to be used as the main menu of a WinForms application. In addition, the control implements a specific visual style with default properties of my own choice. Other users may have different requirements and so one option if you require a different menu appearance is to derive your own class from FlatMenu. Both FlatMenuBar and FlatPopupMenu can be used as examples of how to implement the four abstract methods which are required. For example, in future, I might be looking at implementing a vertical navigation bar using this framework, with options for left or right-aligning text, etc.

History

  • November 9th, 2005
    • Initial revision
  • November 10th, 2005
    • Added a second download link for just the source (and binaries) packaged into MenuControls class library (DLL)
    • Changed the MenuId property to be non-browsable. Also added some minimal design mode-only drawing
  • November 12th, 2005
    • Added InvalidateMenu() to refresh the control when certain properties are changed (primarily for design mode)
  • November 14th, 2005
    • Set the TabStop property to false by default and made more properties non-browsable.
  • January 30th, 2006
    • Added new properties: EnableBorderDrawing, EnableHoverBorderDrawing, EnableHoverBackDrawing, and HoverFont
    • Replaced SecondaryBackColor and HoverBackColor properties with BackGradientColor and HoverBackGradientColor. Allows the menu bar and hover rectangle to be drawn with a gradient background
    • Added Popup property to allow access to the child popup menu. Useful for applying different appearance settings for child popup menus
    • Added support for checked/radio menu items
    • Allow main form in the demo app to be resized larger
    • Added separate downloads for VS 2003 and VS 2005. Each zip file contains the full source code and includes a release version of the MenuControls class library (DLL).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)