Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

ActionMenuStrip<T>/OptionMenuStrip<T>: Generic MenuStrips Configured by an Enum T

3.89/5 (8 votes)
7 Jun 2006CPOL3 min read 1   363  
MenuStrips automatically filled using enums as templates.

Sample Image

Introduction

These controls rely on the same basic idea as the EnumGroupBox - a semi-automatic GroupBox control: configure GUI elements by enums already used in your business logic. In contrast, this submission generates menus as generic types, configured by an enum as the type parameter. ActionMenuStrip<T> encapsulates a set of independent actions (clicking a menu button), while OptionMenuStrip<T> represents a choice of (exclusive) options (checking a radiobutton/checkbox).

Background

Both controls derive from System.Windows.Forms.ContextMenuStrip, so they can be assigned to the Control.ContextMenuStrip/ ToolStripDropDownButton.DropDown properties directly, or used as a ToolStripDropDownMenu in larger menus. For all enum members, the corresponding menu items are created, internally identified by a name composed of an optional common base name and the enum member name. It can be specified whether a member with zero value constitutes a valid option, or if it will be neglected. Enum members at or above a certain value (i.e., 'Last' or 'All') can be excluded.

The text of menu items is either the enum member name itself, or a string array can be passed, which allows localization or the use of accelerators. A third alternative consists of assigning a delegate which returns the text for an item.

ocActionMenuStrip<T>

Its public interface looks like this (minor and overloaded members stripped):

C#
public class ocActionMenuStrip<T> : ContextMenuStrip, 
             INotifyPropertyChanged where T : struct
{
    public event EventHandler ActionClicked
    
    public ocActionMenuStrip()
    
    public delegate string GetActionMenuText(T item)
    public GetActionMenuText MenuTexts { set; }
    
    public T ActionEnabled { get; set; }
    public T ActionVisible { get; set; }
    
    public T Action { get; set; }
    public ToolStripItemCollection ActionItems { get; }

    public ToolStripItemCollection ActionAddItems(
           string baseItemName, bool includeZero, 
           T excludeAtMember, string[] menutext)
}

With the ActionEnabled/ActionVisible properties, individual menu items can be disabled/hidden (i.e., on the Opening event). The enum T must be bitwise (flagged) to use these features. Using the Items property of the base class, other ToolStripMenuItems may be added, without deterring functionality. In this case, the ActionItems property returns the subset of items maintained by the derived class, otherwise it equals the Items property.

ocOptionMenuStrip<T>

C#
public class ocOptionMenuStrip<T> : ContextMenuStrip, 
                   INotifyPropertyChanged where T : struct
{
    public event EventHandler OptionChanged
    public event PropertyChangedEventHandler PropertyChanged

    public ocOptionMenuStrip()

    public delegate string GetOptionMenuText(T item)
    public GetOptionMenuText MenuTexts { set; }

    public bool OptionExclusive { get; set; }
    public bool CancelClosingOnCheck { get; set; }
    public T OptionEnabled { get; set; }
    public T OptionVisible { get; set; }
    
    public T Option { get; set; }
    public T OptionMember { get; }
    public ToolStripItemCollection OptionItems { get; }

    public ToolStripItemCollection OptionAddItems( 
           string baseItemName, bool includeZero, 
           T excludeAtMember, string[] menutext)
}

The OptionExclusive property specifies whether the menu is set up for exclusive or individual checking of items. Exclusive menus are rendered with radio buttons, the individual ones use the standard checkmark images. With the CancelClosingOnCheck property set, the menu stays open when an item is checked. The implementation of the INotifyPropertyChanged interface allows data-binding the Option property to UserSettings. The OptionMember property identifies the enum member which caused the last change of an individual menu.

Using the code

As there is no design-time support (I simply don't know how to achieve this with generic controls), all declarations must be hand-coded. An example configuring an exclusive option menu using enum names, excluding zero and the last member, is shown here:

C#
[Flags]
private enum MyEnum {none = 0, first = 1, 
                     second = 2, third = 4, all = 7}
private ocOptionMenuStrip<MyEnum> oms;

    oms = new ocOptionMenuStrip<MyEnum>();
    oms.MenuTexts = delegate(MyEnum item)
    { 
        return (item == MyEnum.first) ? "Special First" : null ;
    }
    oms.OptionAddItems(null, false, MyEnum.all);
    oms.OptionEnabled = MyEnum.all & ~MyEnum.second;
    oms.OptionChanged += this.oms_OptionChanged;

void oms_OptionChanged(object sender, EventArgs e)
{
    switch (oms.Option)
    {
        case MyEnum.first:
            // ...
    }
}

The two included demo projects show most of the available possibilities.

Points of Interest

  • How to achieve design-time support with generic controls?
  • Your comments and suggestions.

History

  • December 2005: OptionMenuStrip published on Planet Source Code.
  • April 2006: revised and republished on my new favourite site, after spotting Peter Schlang's article.
  • Version 1.0, June 7th 2006:
    • overloaded the XXXAddItems methods with params to exclude members.
    • now use a delegate for setting the menu texts.
    • Bug fix: included zero member could not be enabled.

    OptionMenuStrip:

    • former OptionBitwise became OptionExclusive with opposite functionality.
    • OptionMember identifies the cause of change.
    • exclusive menu drawn with radio button images.

License

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