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):
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 ToolStripMenuItem
s 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>
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:
[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.