Click here to Skip to main content
11,640,297 members (61,993 online)
Click here to Skip to main content

A Magical Edit Menu Manager

, 22 Feb 2004 CPOL 298.1K 704 156
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Tom Clement
Team Leader
United States United States
I've been programming in C, C++, Visual Basic and C# for almost 30 years. I've worked at Sierra Systems, ViewStar, Mosaix, Lucent, Avaya, Avinon, Apptero and now Serena in various roles over my career.

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Jerry Medved7-Dec-10 11:38
memberJerry Medved7-Dec-10 11:38 
GeneralRe: My vote of 5 Pin
Tom Clement6-Jun-11 21:49
memberTom Clement6-Jun-11 21:49 
GeneralAdding a Context Menu Pin
Mdavis299-Oct-09 19:50
memberMdavis299-Oct-09 19:50 
GeneralRe: Adding a Context Menu Pin
Tom Clement10-Oct-09 5:15
memberTom Clement10-Oct-09 5:15 
GeneralRe: Adding a Context Menu Pin
Mdavis2911-Oct-09 18:43
memberMdavis2911-Oct-09 18:43 
GeneralRe: Adding a Context Menu Pin
Tom Clement12-Oct-09 0:35
memberTom Clement12-Oct-09 0:35 
GeneralVery Handy! Pin
plamb31-Dec-08 8:28
memberplamb31-Dec-08 8:28 
GeneralRe: Very Handy! Pin
Tom Clement31-Dec-08 12:03
memberTom Clement31-Dec-08 12:03 
QuestionMenu Item under Edit appear multiple times? Pin
elmasri29-Feb-08 7:36
memberelmasri29-Feb-08 7:36 
QuestionRe: Menu Item under Edit appear multiple times? Pin
elmasri29-Feb-08 10:59
memberelmasri29-Feb-08 10:59 
AnswerRe: Menu Item under Edit appear multiple times? Pin
elmasri5-Mar-08 8:36
memberelmasri5-Mar-08 8:36 
GeneralRe: Menu Item under Edit appear multiple times? Pin
Tom Clement5-Mar-08 8:41
memberTom Clement5-Mar-08 8:41 
QuestionControl.FromChildHandle() Pin
erikest18-Sep-07 9:01
membererikest18-Sep-07 9:01 
AnswerRe: Control.FromChildHandle() [modified] Pin
Tom Clement19-Sep-07 5:54
memberTom Clement19-Sep-07 5:54 
GeneralThis is really great. Pin
Rob Magee17-Jul-07 10:09
memberRob Magee17-Jul-07 10:09 
GeneralRe: This is really great. Pin
Tom Clement17-Jul-07 13:53
memberTom Clement17-Jul-07 13:53 
GeneralUsing in a Visual Basic Project Pin
paix12014-Mar-07 22:15
memberpaix12014-Mar-07 22:15 
GeneralRe: Using in a Visual Basic Project Pin
Tom Clement15-Mar-07 6:48
memberTom Clement15-Mar-07 6:48 
GeneralRe: Using in a Visual Basic Project Pin
paix12015-Mar-07 7:56
memberpaix12015-Mar-07 7:56 
GeneralRe: Using in a Visual Basic Project Pin
paix12015-Mar-07 19:21
memberpaix12015-Mar-07 19:21 
GeneralRe: Using in a Visual Basic Project Pin
Tom Clement16-Mar-07 5:20
memberTom Clement16-Mar-07 5:20 
QuestionEnable/Disable Toolbar Buttons?!? Pin
noah_vb19-Nov-06 22:23
membernoah_vb19-Nov-06 22:23 
AnswerRe: Enable/Disable Toolbar Buttons?!? [modified] Pin
Tom Clement21-Nov-06 13:45
memberTom Clement21-Nov-06 13:45 
GeneralRe: Enable/Disable Toolbar Buttons?!? Pin
noah_vb24-Nov-06 7:44
membernoah_vb24-Nov-06 7:44 
GeneralRe: Enable/Disable Toolbar Buttons?!? Pin
Tom Clement26-Dec-06 13:03
memberTom Clement26-Dec-06 13:03 
GeneralBug with no text available Pin
jeffb4218-Sep-06 8:43
memberjeffb4218-Sep-06 8:43 
GeneralRe: Bug with no text available Pin
Tom Clement18-Sep-06 9:53
memberTom Clement18-Sep-06 9:53 
GeneralUsing in Visual Studio 2005 Pin
bashiwala24-Aug-06 9:28
memberbashiwala24-Aug-06 9:28 
GeneralRe: Using in Visual Studio 2005 Pin
wkiebalo7-Mar-08 19:19
memberwkiebalo7-Mar-08 19:19 
GeneralKudos! Pin
rmdw3-May-06 7:58
memberrmdw3-May-06 7:58 
GeneralRe: Kudos! Pin
Tom Clement3-May-06 10:33
memberTom Clement3-May-06 10:33 
QuestionNot working with VS2003/VS2005 Pin
johndsp19-Jan-06 22:30
memberjohndsp19-Jan-06 22:30 
AnswerRe: Not working with VS2003/VS2005 Pin
Tom Clement20-Jan-06 5:17
memberTom Clement20-Jan-06 5:17 
AnswerRe: Not working with VS2003/VS2005 Pin
timdw18-Feb-06 18:26
membertimdw18-Feb-06 18:26 
GeneralRe: Not working with VS2003/VS2005 Pin
Tom Clement19-Feb-06 6:38
memberTom Clement19-Feb-06 6:38 
GeneralRe: Not working with VS2003/VS2005 Pin
Chals28-Feb-06 23:37
memberChals28-Feb-06 23:37 
GeneralRe: Not working with VS2003/VS2005 Pin
Micktion6-Feb-07 20:33
memberMicktion6-Feb-07 20:33 
GeneralRe: Not working with VS2003/VS2005 Pin
Ramsey Callaway9-Jun-07 11:14
memberRamsey Callaway9-Jun-07 11:14 
GeneralSpace key Pin
saeidkhan17-Jan-06 7:15
membersaeidkhan17-Jan-06 7:15 
GeneralRe: Space key Pin
Tom Clement17-Jan-06 12:40
memberTom Clement17-Jan-06 12:40 
GeneralAwesome work Pin
rkriger1-Jan-06 13:44
memberrkriger1-Jan-06 13:44 
GeneralRe: Awesome work Pin
Tom Clement1-Jan-06 18:09
memberTom Clement1-Jan-06 18:09 
GeneralRe: Awesome work Pin
rkriger19-Feb-07 15:01
memberrkriger19-Feb-07 15:01 
GeneralInternationalization Pin
Danny DB16-Oct-05 21:18
memberDanny DB16-Oct-05 21:18 
GeneralRe: Internationalization Pin
Tom Clement17-Oct-05 4:13
memberTom Clement17-Oct-05 4:13 
GeneralRe: Internationalization Pin
Danny DB18-Oct-05 3:25
memberDanny DB18-Oct-05 3:25 
GeneralRe: Internationalization Pin
Tom Clement18-Oct-05 8:41
memberTom Clement18-Oct-05 8:41 
GeneralRe: Internationalization Pin
A55imilate8-Sep-06 3:47
memberA55imilate8-Sep-06 3:47 
GeneralListView/TreeView item editors Pin
deadly2-Aug-05 23:46
memberdeadly2-Aug-05 23:46 
GeneralRe: ListView/TreeView item editors Pin
Tom Clement3-Aug-05 4:21
memberTom Clement3-Aug-05 4:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150731.1 | Last Updated 23 Feb 2004
Article Copyright 2004 by Tom Clement
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid