Surprisingly, adding to the default context menu in WPF is easier than in WinForms, or at least a little more high level. You don't have to touch Windows Messages in WndProc. This solution is based on SharpAnalytics

First off you need to extend the object you're going to add to the context menu of. This makes sense as you are literally extending the object's context menu. In my case I'm modifying LinkedTextBox, itself an extension of the System.Windows.Controls.TextBox. Our extension uses reflection magic to grab the contextmenu for the given type after it's been created. It has a method InjectIntoDefaultMenu which allows us to hook into that menu and add items to it.

namespace Test
{
public static class LinkedTextBoxExtensions
{
private static Type _editorContextMenuType;
private static MethodInfo _editorContextMenuAddMenuItemsMethod;
private static Type _textBoxBaseType;
private static FieldInfo _textBoxBaseTextEditorField;
private static PropertyInfo _contextMenuEventArgsUserInitiatedProperty;

static LinkedTextBoxExtensions()
{
_editorContextMenuType = typeof(TextBox).Assembly.GetType("System.Windows.Documents.TextEditorContextMenu+EditorContextMenu");
_editorContextMenuAddMenuItemsMethod = _editorContextMenuType.GetMethod("AddMenuItems",
BindingFlags.NonPublic |
BindingFlags.Instance);
_textBoxBaseType = typeof(TextBoxBase);
_textBoxBaseTextEditorField = _textBoxBaseType
.GetField("_textEditor", BindingFlags.NonPublic | BindingFlags.Instance);

_contextMenuEventArgsUserInitiatedProperty = typeof(ContextMenuEventArgs).GetProperty("UserInitiated",
BindingFlags.NonPublic |
BindingFlags.Instance);
}

public static void InjectIntoDefaultMenu(this TextBoxBase textBoxBase, ContextMenuEventArgs e, Action callBaseContextMenuOpening, params System.Windows.Controls.MenuItem[] items)
{
var contextMenu = (ContextMenu)Activator.CreateInstance(_editorContextMenuType, true);
textBoxBase.ContextMenu = contextMenu;

callBaseContextMenuOpening(e);

_editorContextMenuAddMenuItemsMethod.Invoke(contextMenu, new[]
{
_textBoxBaseTextEditorField.GetValue(textBoxBase),
_contextMenuEventArgsUserInitiatedProperty.GetValue(e, null)
});

if (contextMenu.Items.Count > 0 && items.Length > 0)
contextMenu.Items.Add(new Separator());

foreach (var item in items)
contextMenu.Items.Add(item);
}
}
}


Now we move onto our class proper. LinkedTextBox has been shortened here to show only the relevant code.
The method ContextMenu_Opening is the standard event handler for ContextMenuOpening. It's here we call InjectIntoDefaultMenu and pass it the extra menu items. For convenience I'm creating the menu items right there in the method. Your context menu might have more items and events. AddToken_Click is just a reference to the event that happens when I click my new menu item.

The constructor for LinkedTextBox adds the ContextMenu_Opening event to my new LinkedTextBox. Above it is a single line this.ContextMenu = null. This line is VERY IMPORTANT. Without it, ContextMenuOpening events will not fire and you won't know why.

Of course in practice you might want to add the ContextMenu events in another control, however I'd advise keeping the constructor with the ContextMenu = null.

namespace Test
{
public class LinkedTextBox : TextBox
{
public LinkedTextBox() : base()
{
this.ContextMenu = null;
ContextMenuOpening += ContextMenu_Opening;
}

public void ContextMenu_Opening(object sender, ContextMenuEventArgs e)
{
var tokenAdd = new MenuItem();
tokenAdd.Header = "Add Token";
tokenAdd.Click += AddToken_Click;
var items = new MenuItem[] { tokenAdd };
this.InjectIntoDefaultMenu(e, p => base.OnContextMenuOpening(p), items);
}
}
}

No comments

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
BBCode format allowed