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:
public class FlatMenuItemList
{
public int Count {...}
public FlatMenuItemList() {...}
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) {...}
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:
public class FlatMenuItem
{
public event System.EventHandler Click;
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 {...}
public FlatMenuItem() {...}
public void NotifyClickEvent() {...}
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:
public abstract class FlatMenu : System.Windows.Forms.Control
{
public event CurrentMenuItemChangeEventHandler
CurrentMenuItemChange;
public int MenuId {...}
public GradientColor BackGradientColor {...}
public Color BorderColor {...}
public Color SeparatorColor {...}
public Color TextColor {...}
public GradientColor HoverBackGradientColor {...}
public Color HoverBorderColor {...}
public Color HoverTextColor {...}
public Color DisabledTextColor {...}
public Font HoverFont {...}
public bool EnableBorderDrawing {...}
public bool EnableHoverBorderDrawing {...}
public bool EnableHoverBackDrawing {...}
public int LeftMargin {...}
public int TopMargin {...}
public int MenuItemSpacing {...}
public int MenuTimerInterval {...}
public bool IsPopupMenu {...}
public FlatMenu Popup {...}
public FlatMenu ParentMenu {...}
public FlatMenuItem ParentMenuItem {...}
public FlatMenuItemList MenuItems {...}
public FlatMenu() {...}
public override string ToString() {...}
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:
public class MainForm : System.Windows.Forms.Form
{
...
public MainForm()
{
InitializeComponent();
LoadMenuBar1();
}
private void LoadMenuBar1()
{
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:
private void LoadMenuBar1()
{
...
FlatMenuItem newItem = new FlatMenuItem();
newItem.Text = "New";
fileItem.MenuItems.Add(newItem);
fileItem.MenuItems.AddSeparator();
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:
private void LoadMenuBar1()
{
...
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:
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:
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):
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:
public class MainForm : System.Windows.Forms.Form
{
...
private void LoadMenuBar1()
{
...
this.flatMenuBar1.CurrentMenuItemChange +=
new CurrentMenuItemChangeEventHandler(
flatMenuBar1_CurrentMenuItemChange);
}
private void flatMenuBar1_CurrentMenuItemChange(
object obj,
CurrentMenuItemChangeEventArgs e)
{
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:
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:
public class MainForm : System.Windows.Forms.Form
{
...
private FlatPopupMenu popupMenu = null;
protected override void Dispose( bool disposing )
{
if ( disposing )
{
...
if ( this.popupMenu != null )
{
this.popupMenu.Dispose();
this.popupMenu = null;
}
}
base.Dispose( disposing );
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if ( e.Button != MouseButtons.Right )
return;
ShowPopupMenu(e.X, e.Y);
}
private void ShowPopupMenu(int x, int y)
{
if ( this.popupMenu == null )
{
this.popupMenu = new FlatPopupMenu();
this.popupMenu.BackColor = Color.Red;
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);
}
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:
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
- 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).