Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Magical Edit Menu Manager

0.00/5 (No votes)
12 Oct 2018 1  
A magical edit menu that works with no connections to the rest of your project

Summary

The code in this article creates a fully functional Edit menu that you can add to a WinForms application by calling one function. You can add support for non-text editing features by implementing a simple interface on controls that display the non-text editing objects.

Introduction

So you've almost finished writing your new application and all that is left are a few finishing touches. You know that you need an edit menu, but what's the best way to hook it into your project? If you have a single edit surface, it's probably a no-brainer, but if your project is like mine, you have many distinct controls that are dynamically loaded as the user context changes and any of them may contain editable constituent controls. You may also have editable task panes and controls in navigation bars that are constantly changing.

If you try to track the state of all these controls, update the menus enabled and visible properties and respond appropriately to menu click events, your code can become a jumble. This article presents a UI design pattern that takes this complex situation and makes it surprisingly (well, surprising to me anyway) simple by deferring menu manipulation until the moment the menu is shown. You end up with a completely independent component that knows nothing about the application hosting the edit menu. The steps are:

  • Respond to the (Edit menu expanded or clicked) event as the menu is about to be shown.
  • Use the Win32 API to determine which window currently has the focus.
  • Use the Control.FromHandle() .NET Framework method to get a reference to the .NET control associated with the focused window.
  • Determine the type of the control that has the focus.
  • Based on the control's type and state, enable or disable and hide or show the various edit menu items.
  • When the user selects one of the menu items, pass the command on to the focused control.

Of course, as always, the devil is in the details. In this article, we'll go step by step through the process of creating the magical edit menu manager and solving the various little problems encountered along the way. When we're done, you'll have a component you can easily reuse in any WinForms project.

Background

As it was with my previous article, I'm sure that readers will have plenty of criticisms, comments and suggestions for improvement. Please feel free to share them, and I'll do my best to update the article accordingly.

Creating the Menu

Our goal is to create a separate module that can easily be reused in any C# application. To this end, we'll create and manage the edit menu inside of our module. All the calling application needs to do is pass in the top level edit menu. The Edit Menu Manager populates it.

Start by creating a new Windows Application Project called MenuManagers. Delete Form1.cs and add a new Class called EditMenuManager. We'll use this to define and manage the edit menu items. Add using directives for some WinForms namespaces.

using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

In the class, declare the menu items.

// Declare the MenuItems to insert under the Edit menu
private MenuItem m_miEdit;
private MenuItem m_miUndo;
private MenuItem m_miRedo;
private MenuItem m_miCut;
private MenuItem m_miCopy;
private MenuItem m_miPaste;
private MenuItem m_miDelete;
private MenuItem m_miDividerRedoCut;
private MenuItem m_miDividerPasteDelete;
private MenuItem m_miSelectAll;
private MenuItem m_miDividerPropertiesSelectAll;
private MenuItem m_miProperties;

Create an enumeration corresponding to the positions of the menu items in the menu. This makes it a little bit easier to respond to menu commands.

[Flags]
private enum MenuIndex
{
  Undo = 0,
  Redo = 1, 
  Divider1 = 2,
  Cut = 3,
  Copy = 4,
  Paste = 5,
  Divider2 = 6,
  Delete = 7,
  SelectAll = 8,
  Divider3 = 9,
  Properties = 10
}

Create a function to create and initialize the menu items and call it from the constructor.

private void CreateMenus()
{
  // Create
  m_miUndo = new MenuItem();
  m_miRedo = new MenuItem();
  m_miCut = new MenuItem();
  m_miCopy = new MenuItem();
  m_miPaste = new MenuItem();
  m_miDelete = new MenuItem();
  m_miDividerRedoCut = new MenuItem();
  m_miDividerPasteDelete = new MenuItem();
  m_miSelectAll = new MenuItem();
  m_miDividerPropertiesSelectAll = new MenuItem();
  m_miProperties = new MenuItem();

  // Initialize
  m_miUndo.Index = (int)MenuIndex.Undo;
  m_miUndo.Text = "&Undo";
  m_miUndo.Shortcut = Shortcut.CtrlZ;
  m_miRedo.Index = (int)MenuIndex.Redo;
  m_miRedo.Text = "&Redo";
  m_miRedo.Shortcut = Shortcut.CtrlY;
  m_miDividerRedoCut.Index = (int)MenuIndex.Divider1;
  m_miDividerRedoCut.Text = "-";
  m_miCut.Index = (int)MenuIndex.Cut;
  m_miCut.Text = "Cu&t";
  m_miCut.Shortcut = Shortcut.CtrlX;
  m_miCopy.Index = (int)MenuIndex.Copy;
  m_miCopy.Text = "&Copy";
  m_miCopy.Shortcut = Shortcut.CtrlC;
  m_miPaste.Index = (int)MenuIndex.Paste;
  m_miPaste.Text = "&Paste";
  m_miPaste.Shortcut = Shortcut.CtrlV;
  m_miDividerPasteDelete.Index = (int)MenuIndex.Divider2;
  m_miDividerPasteDelete.Text = "-";
  m_miDelete.Index = (int)MenuIndex.Delete;
  m_miDelete.Text = "&Delete";
  m_miDelete.Shortcut = Shortcut.Del;
  m_miSelectAll.Index = (int)MenuIndex.SelectAll;
  m_miSelectAll.Text = "Select A&ll";
  m_miSelectAll.Shortcut = Shortcut.CtrlA;
  m_miDividerPropertiesSelectAll.Index = (int)MenuIndex.Divider3;
  m_miDividerPropertiesSelectAll.Text = "-";
  m_miProperties.Index = (int)MenuIndex.Properties;
  m_miProperties.Text = "Pr&operties";
  m_miProperties.Shortcut = Shortcut.F4;
}

Now, add a public method to let the calling application add these menus to its top level Edit menu. While we're at it, hook the Popup event on the Edit menu so we can tell when the menu is being clicked, and hook the Click events on the other menu items.

// The main entry point for this module. Used for initializing
// the Edit menu of the Windows Forms application.
public void ConnectMenus(MenuItem miEdit)
{
  // Subsequent calls are ignored.
  if( m_miEdit == null )
  {
    CreateMenus();
    m_miEdit = miEdit;
    m_miEdit.MenuItems.AddRange(
      new MenuItem[] {
        m_miUndo,
        m_miRedo,
        m_miDividerRedoCut,
        m_miCut,
        m_miCopy,
        m_miPaste,
        m_miDividerPasteDelete,
        m_miDelete,
        m_miSelectAll,
        m_miDividerPropertiesSelectAll,
        m_miProperties});

    m_miEdit.Popup += new System.EventHandler(Edit_Popup);
    m_miUndo.Click += new System.EventHandler(Menu_Click);
    m_miRedo.Click += new System.EventHandler(Menu_Click);
    m_miCut.Click += new System.EventHandler(Menu_Click);
    m_miCopy.Click += new System.EventHandler(Menu_Click);
    m_miPaste.Click += new System.EventHandler(Menu_Click);
    m_miDelete.Click += new System.EventHandler(Menu_Click);
    m_miSelectAll.Click += new System.EventHandler(Menu_Click);
    m_miProperties.Click += new System.EventHandler(Menu_Click);
  }
}

This is the only method you'll need to call from your application.

Win32 API Utilities

When the user clicks on the Edit menu, the first thing we need to do is find out what control currently has the focus. To do this, we'll need to call a Win32 API method. Since we will need a number of Win32 methods, let's take a minute to create a utility class for them. Create a new class called Win32API in a separate file. We'll put all API calls and related utility methods in it.

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

Now add some declarations and imports to the class.

public class Win32API
{
  [StructLayout(LayoutKind.Sequential)]
  public struct GETTEXTLENGTHEX
  {
    public Int32 uiFlags;
    public Int32 uiCodePage;
  }

  public const int WM_USER = 0x400;
  public const int EM_CUT = 0x300;
  public const int EM_COPY = 0x301;
  public const int EM_PASTE = 0x302;
  public const int EM_CLEAR = 0x303;
  public const int EM_UNDO = 0x304;
  public const int EM_CANUNDO = 0xC6;
  public const int EM_CANPASTE = WM_USER + 50;
  public const int EM_GETTEXTLENGTHEX = WM_USER + 95;

  /// Windows API SendMessage functions
  [DllImport("user32.dll", CharSet = CharSet.Auto, 
    SetLastError = true)]
  public static extern int SendMessage(IntPtr hWnd, 
    int msg, int wParam, int lParam);
  [DllImport("user32.dll", EntryPoint="SendMessage", 
    CharSet=CharSet.Auto )]
  public static extern int SendMessage( IntPtr hWnd, int Msg,
    ref GETTEXTLENGTHEX wParam, IntPtr lParam);
  
  // Return the handle of the window that has the focus.
  [DllImport("user32.dll")]
  public static extern IntPtr GetFocus();
  
  /// Windows API GetParent function
  [DllImport("user32", SetLastError = true)]
  public extern static IntPtr GetParent(IntPtr hwnd);

  // The constructor.  Not used since all methods are static.
  public Win32API(){}

Add a method to obtain the framework control that is associated with the currently focused control.

// Return the Framework control associated with the specified handle.
public static Control GetFrameworkControl(IntPtr hControl)
{
  Control rv = null;
  if( hControl.ToInt32() != 0 )
  {
    rv = Control.FromHandle(hControl);
    // Try the parent, since with a ComboBox, we get a 
    // handle to an inner control.
    if( rv == null )
    rv = Control.FromHandle(GetParent(hControl));
  }
  return rv;
}

Finally, we add a few methods to fill in the gaps in the WinForms API. These will be used to provide Cut/Copy/Paste/Undo functionality to the ComboBox control, and to work around a bug in the RichTextBox control that causes the Undo/Redo buffer to be cleared when the Text or TextLength properties are read(!).

// Edit commands for the inner textbox in the ComboBox control.
public static void Undo(IntPtr hEdit)   
{
  SendMessage(hEdit, EM_UNDO, 0, 0);
}
public static void Cut(IntPtr hEdit)    
{
  SendMessage(hEdit, EM_CUT, 0, 0);
}
public static void Copy(IntPtr hEdit)   
{
  SendMessage(hEdit, EM_COPY, 0, 0);
}
public static void Paste(IntPtr hEdit)  
{
  SendMessage(hEdit, EM_PASTE, 0, 0);
}
public static bool CanUndo(IntPtr hEdit)
{
  return SendMessage(hEdit, EM_CANUNDO, 0, 0) != 0;
}

// Determine whether the clipboard contains any format 
// that can be pasted into a rich text box.
public static bool CanPasteAnyFormat(IntPtr hRichText)
{
  return SendMessage(hRichText, EM_CANPASTE, 0,0) != 0;
}
// Determine the length of a control's Text. Required since using the 
// RichTextBox .Length property wipes out the Undo/Redo buffer.
public static int GetTextLength(IntPtr hControl)
{
  GETTEXTLENGTHEX lpGTL = new GETTEXTLENGTHEX();
  lpGTL.uiFlags = 0;
  lpGTL.uiCodePage = 1200; // Unicode
  return SendMessage(hControl, EM_GETTEXTLENGTHEX, 
    ref lpGTL, IntPtr.Zero);
}

Responding to the Edit Menu Popup Event

We now have everything we need to setup the menu when the user clicks on the edit menu item. We use the GetFocus() API call to obtain the handle of the currently focused control, then call the utility function GetFrameworkControl() to find the first parent of that control that corresponds to a framework object. Usually, this is the control with the handle returned by GetFocus(), but sometimes, like with the ComboBox, it's the parent.

Enable or Disable Menu Items

Now that we've obtained a reference to the focused control, we can enable or disable the edit menu items based on its state. But before handling the Popup event, let's define a flags enum to represent the edit state of the control. This way, a single bit field variable can easily describe the condition of the menu. We use the [Flags] attribute since the values will be combined in a bit flag.

// Declare an enumeration to represent the 
// visible/invisible enabled/disabled
// state of the menu.
[Flags]
private enum EditState
{
  None = 0x0,
  UndoVisible = 0x1,
  RedoVisible = 0x2,
  UndoEnabled = 0x4,
  RedoEnabled = 0x8,
  CutEnabled = 0x10,
  CopyEnabled = 0x20,
  PasteEnabled = 0x40,
  SelectAllEnabled = 0x80,
  DeleteEnabled = 0x100,
  RenameEnabled = 0x200,
  PropertiesEnabled = 0x400
}

TextBox and RichTextBox Menus

Now, write the Popup event handler. We'll start with TextBoxBase to handle both TextBox and RichTextBox controls and call the function GetTextBoxEditState() to return the state of those classes of controls. Later, we'll add support for other types of controls. After obtaining the bit flag determining the editable state of the TextBoxBase, we set the corresponding properties on the menu items.

// When the user clicks on the Edit menu, determine the focused control,
// call a method to get state flags, then setup the edit menu based 
// on the flags.
private void Edit_Popup(object sender, System.EventArgs e)
{
  IntPtr hFocus = Win32API.GetFocus();
  Control ctlFocus = Win32API.GetFrameworkControl(hFocus);
  EditState eEditState = EditState.None;

  if( ctlFocus is TextBoxBase )
    eEditState = GetTextBoxEditState((TextBoxBase)ctlFocus);
  
  // Show or hide and enable or disable menu 
  // controls according to eEditState.
  m_miUndo.Visible = (eEditState & EditState.UndoVisible) != 0;
  m_miRedo.Visible = (eEditState & EditState.RedoVisible) != 0;
  m_miDividerRedoCut.Visible = (m_miUndo.Visible == true 
      || m_miRedo.Visible == true);
  m_miUndo.Enabled = (eEditState & EditState.UndoEnabled) != 0;
  m_miRedo.Enabled = (eEditState & EditState.RedoEnabled) != 0;
  m_miCut.Enabled = (eEditState & EditState.CutEnabled) != 0;
  m_miCopy.Enabled = (eEditState & EditState.CopyEnabled) != 0;
  m_miPaste.Enabled = (eEditState & EditState.PasteEnabled) != 0;
  m_miDelete.Enabled = (eEditState & EditState.DeleteEnabled) != 0;
  m_miSelectAll.Enabled = (eEditState & EditState.SelectAllEnabled) != 0;
  m_miProperties.Enabled = (eEditState & EditState.PropertiesEnabled) != 0;
}

The GetTextBoxEditState() method does the work:

// Obtain EditState flags for TextBox and RichTextBox controls.
private EditState GetTextBoxEditState(TextBoxBase textbox)
{
  // Set Booleans defining the textbox state.
  bool bWritable = (textbox.ReadOnly == false && textbox.Enabled == true);
  bool bTextSelected = (textbox.SelectionLength > 0);
  // Cannot use textbox.TextLength, because that 
  // wipes out Undo/Redo buffer in RichTextBox
  bool bHasText = Win32API.GetTextLength(textbox.Handle) > 0;
  bool bIsRichText = (textbox is RichTextBox);

  // Use the Booleans to set the EditState flags.
  EditState eState = EditState.UndoVisible;
  if( bIsRichText )
  {
    eState |= EditState.RedoVisible;
    if( ((RichTextBox)textbox).CanRedo )
      eState |= EditState.RedoEnabled;
  }
  if( textbox.CanUndo )
    eState |= EditState.UndoEnabled;
  if( textbox.CanSelect )
    eState |= EditState.SelectAllEnabled;
  if( bTextSelected )
    eState |= EditState.CopyEnabled;
  if( bWritable )
  {
    if( bTextSelected )
    {
      eState |= EditState.CutEnabled;
      eState |= EditState.DeleteEnabled;
    }
    if( bIsRichText )
    {
      if( Win32API.CanPasteAnyFormat(textbox.Handle) )
        eState |= EditState.PasteEnabled;
    }
    else // TextBox
    {
      if( Clipboard.GetDataObject().GetDataPresent(DataFormats.Text) ) 
      eState |= EditState.PasteEnabled;
    }
  }
  return eState;
}

The method looks at various aspects of the TextBox (or RichTextBox) state, like whether it is enabled, how much text is selected and so forth. And based on this, determines whether various Edit operations can be done on the contained text. Most of it is pretty straightforward. The one trick that's not obvious, is that you cannot use the TextBoxBase.TextLength method to determine whether there is any text to select. The problem is that a bug in the RichTextBox causes the reading of this property (or the RichTextBox.Text property for that matter) to wipe out the entire Undo/Redo buffer in the RichTextBox. In production code, you might consider deriving a control from RichTextBox and overriding the Text and TextLength methods with ones that obtain the data using the API.

ComboBox (DropDown style) Control

Another common control that we'd like the edit menu to operate against is the ComboBox when its style is DropDown. We start by adding a new function call to the Edit_Popup event handler.

...
else if( ctlFocus is ComboBox && 
        ((ComboBox)ctlFocus).DropDownStyle == ComboBoxStyle.DropDown )
  eEditState = GetComboBoxEditState(hFocus, (ComboBox)ctlFocus);

...

And write the code to get the ComboBox state.

// Obtain EditState flags for ComboBox controls.
private EditState GetComboBoxEditState(IntPtr hEdit, ComboBox combobox)
{
  // Set Booleans defining the ComboBox state.
  bool bWritable = combobox.Enabled;
  bool bClipboardText = 
    Clipboard.GetDataObject().GetDataPresent(DataFormats.Text);
  bool bTextSelected = combobox.SelectionLength > 0;
  bool bHasText = combobox.Text.Length > 0;

  // Use the Booleans to set the EditState flags.
  EditState eState = EditState.UndoVisible;
  if( Win32API.CanUndo(hEdit) )
    eState |= EditState.UndoEnabled;

  if( bWritable )
  {
    if( bTextSelected )
    { 
      eState |= EditState.CutEnabled;
      eState |= EditState.DeleteEnabled;
    }
    if( bClipboardText )
      eState |= EditState.PasteEnabled;
  }
  if( bTextSelected )
    eState |= EditState.CopyEnabled;
  if( bHasText )
    eState |= EditState.SelectAllEnabled;
  return eState;
}

As with the TextBox, we again look at various aspects of the ComboBox to determine which menu actions are possible, and set the flags accordingly. Since there is no CanUndo property on the ComboBox, we pass the handle of the underlying TextBox control to the Win32API.CanUndo() method to determine if the previous action can be undone.

Custom Controls

While it's nice to have automatic support for standard text editing controls, many interesting edit behaviors are against non-text objects in controls you have defined. For example, you might be implementing a graphical editor that supports cut/copy and paste. Our menu can easily work with these controls so long as they've implemented an interface that lets us both obtain state information, and issue the corresponding commands to the control. We'll define a public interface called ISupportsEdit and define it at the end of the file.

// Define the public interface for user defined editable controls.
public interface ISupportsEdit
{
  bool UndoVisible { get;}
  bool CanUndo { get; }
  void Undo();
  bool RedoVisible {get;}
  bool CanRedo { get; }
  void Redo();
  bool CanCut { get;}
  void Cut();
  bool CanCopy { get; }
  void Copy();
  bool CanPaste { get; }
  void Paste();
  bool CanSelectAll { get; }
  void SelectAll();
  bool CanDelete { get; }
  void Delete();
  bool CanShowProperties { get; }
  void ShowProperties();
}

Now, let's modify the Edit_Popup event handler to check for controls that support this interface. After the checks for TextBoxBase and ComboBox, we add the following code:

else
{ 
  // If this is not a simple control, search up the parent chain for 
  // a custom editable control.
  ISupportsEdit ctlEdit = GetISupportsEditControl(ctlFocus);
  if( ctlEdit != null )
    eEditState = GetISupportsEditState(ctlEdit);
}

The GetISupportsEditControl() method checks the control passed in to see if it implements the ISupportsEdit interface. If it doesn't, it travels up the parent chain until it finds a control that does, or fails, returning null. If we find one, we call the GetISupportsEditState() method to query it for menu state.

// Takes a control and traverses the parent chain until it finds a control
// that supports the ISupportsEdit interface. Returns that control, or null
// if none is found.
private ISupportsEdit GetISupportsEditControl(Control ctlFocus)
{
  while( !(ctlFocus is ISupportsEdit) && ctlFocus != null )
    ctlFocus = ctlFocus.Parent;
  return (ISupportsEdit)ctlFocus;
}

// Set EditState flags for ISupportsEdit controls.
private EditState GetISupportsEditState(ISupportsEdit control)
{
  EditState eState = EditState.None;
  if( control.UndoVisible ) eState |= EditState.UndoVisible;
  if( control.CanUndo ) eState |= EditState.UndoEnabled;
  if( control.RedoVisible ) eState |= EditState.RedoVisible;
  if( control.CanRedo ) eState |= EditState.RedoEnabled;
  if( control.CanCut ) eState |= EditState.CutEnabled;
  if( control.CanCopy ) eState |= EditState.CopyEnabled;
  if( control.CanPaste ) eState |= EditState.PasteEnabled;
  if( control.CanSelectAll ) eState |= EditState.SelectAllEnabled;
  if( control.CanDelete ) eState |= EditState.DeleteEnabled;
  if( control.CanShowProperties ) eState |= EditState.PropertiesEnabled;
  return eState;
}

Issue the Edit Command

We're almost done. All that's left is to write an event handler for the menu click events and call methods for the various types of controls to do the requested edit operation. We start by writing the event handler for the menu click events.

// Click handler for all edit menus. Determine the focused window
// and framework control, then call a method to take the appropriate 
// action, based on the type of the control.
private void Menu_Click(object sender, System.EventArgs e)
{
  MenuItem miClicked = sender as MenuItem;
  IntPtr hFocus = Win32API.GetFocus();
  Control ctlFocus = Win32API.GetFrameworkControl(hFocus);
  MenuIndex menuIndex = (MenuIndex)miClicked.Index;
  if( ctlFocus is TextBoxBase )
    DoTextBoxCommand((TextBoxBase)ctlFocus, menuIndex);
  else if( ctlFocus is ComboBox && 
    ((ComboBox)ctlFocus).DropDownStyle == ComboBoxStyle.DropDown ) 
    DoComboBoxCommand(hFocus, (ComboBox)ctlFocus, menuIndex);
  else
  {
    ISupportsEdit ctlEdit = GetISupportsEditControl(ctlFocus);
    if (ctlEdit != null )
      DoISupportsEditCommand(ctlEdit, menuIndex);
  }
}

This code gets the focused control, determines its type and passes the control and the menu command on to a method for performing the command. The method for Textbox and RichTextBox looks like this:

// Perform the command associated with MenuIndex on the specified TextBoxBase.
private void DoTextBoxCommand(TextBoxBase textbox, MenuIndex menuIndex)
{
  switch(menuIndex)
  {
    case MenuIndex.Undo: textbox.Undo(); break;
    case MenuIndex.Redo:
      if( textbox is RichTextBox )
      {
        RichTextBox rt = (RichTextBox)textbox;
        rt.Redo();
      }
      break;
    case MenuIndex.Cut: textbox.Cut(); break;
    case MenuIndex.Copy: textbox.Copy(); break;
    case MenuIndex.Paste: textbox.Paste(); break;
    case MenuIndex.Delete: textbox.SelectedText = ""; break;
    case MenuIndex.SelectAll: textbox.SelectAll(); break;
    case MenuIndex.Properties: break;
  }
}

The code is fairly simple, with the only difference between the TextBox and RichTextBox is that RichTextBox supports Redo(), so that is called out separately.

The methods for ComboBox and ISupportsEdit are equally straightforward:

// Perform the command associated with MenuIndex 
// on the specified ComboBox.
private void DoComboBoxCommand(IntPtr hEdit, 
    ComboBox combobox, MenuIndex menuIndex)
{
  switch(menuIndex)
  {
    case MenuIndex.Undo: Win32API.Undo(hEdit); break;
    case MenuIndex.Cut: Win32API.Cut(hEdit); break;
    case MenuIndex.Copy: Win32API.Copy(hEdit); break;
    case MenuIndex.Paste: Win32API.Paste(hEdit); break;
    case MenuIndex.SelectAll: combobox.SelectAll(); break;
    case MenuIndex.Delete: combobox.SelectedText = ""; break;
  }
}

// Perform the command associated with MenuIndex on 
// the specified ISupportsEdit control.
private void DoISupportsEditCommand(
    ISupportsEdit control, MenuIndex menuIndex)
{
  switch(menuIndex)
  {
    case MenuIndex.Undo: control.Undo(); break;
    case MenuIndex.Redo: control.Redo(); break;
    case MenuIndex.Cut: control.Cut(); break;
    case MenuIndex.Copy: control.Copy(); break;
    case MenuIndex.Paste: control.Paste(); break;
    case MenuIndex.SelectAll: control.SelectAll(); break;
    case MenuIndex.Delete: control.Delete(); break;
    case MenuIndex.Properties: control.ShowProperties(); break;
  }
}

The Test Project

I've included a test project in the download that illustrates how to use this code. It has a form with TextBox, RichTextBox, ComboBox and custom TreeView based controls (TestEditableUserControl) on it. The TestEditableUserControl supports the MenuManagers.ISupportsEdit interface which lets you cut, copy and paste nodes between the trees. It also implements a trivial Property dialog (a MessageBox) that displays the node Text as an illustration of the Properties menu.

Conclusion

Well, believe it or not, we're done. If you're using WinForms menus, all you have to do is include this project in your solution, define a single Edit top level menu and pass it into the ConnectMenus() method. If you have any custom controls that support editing capability, you'll want to implement the ISupportsEdit interface on them. In my case, it was pretty easy, since the controls already had context menus with all the edit commands on them.

If you're using another menu system, you'll need to modify the code accordingly, but all the basic ideas of the article still apply. On this front, there is one caveat. If the menu system you're using takes the focus (it shouldn't) then the code in this article will not work, since the focused control will always appear to be the menu itself.

Anyway, good luck, have fun and let me know how it turns out if you do use this code. To be clear, you may use this code for any purpose whatsoever, personal or commercial.

History

  • 1-30-2004 - First version
  • 2-2-2004 - Second version. Minor editorial changes to the text of the article.
  • 2-9-2004 - Third version. Fixed the bug pointed out by Dreamwolf (thank you). More editorial changes to the text.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here