How to: Use a Custom Context Menu with a TextBox

This example shows how to define and implement a simple custom context menu for a TextBox.

Define a Custom Context Menu

The following Extensible Application Markup Language (XAML) example defines a TextBox control that includes a custom context menu.

The context menu is defined using a ContextMenu element. The context menu itself consists of a series of MenuItem elements and Separator elements. Each MenuItem element defines a command in the context menu; the Header attribute defines the display text for the menu command, and the Click attribute specifies a handler method for each menu item. The Separator element simply causes a separating line to be rendered between the previous and subsequent menu items.

<TextBox
  Name="cxmTextBox" 
  Grid.Row="1"
  AcceptsReturn="True"
  AcceptsTab="True"
  VerticalScrollBarVisibility="Visible"
  TextWrapping="Wrap"
>
  <TextBox.ContextMenu>
    <ContextMenu 
      Name="cxm"
      Opened="CxmOpened"
    >
      <MenuItem 
        Header="Cut"
        Name="cxmItemCut" 
        Click="ClickCut" 
      />
      <MenuItem 
        Header="Copy" 
        Name="cxmItemCopy"
        Click="ClickCopy" 
      />
      <MenuItem 
        Header="Paste"
        Name="cxmItemPaste"
        Click="ClickPaste" 
      />
      <Separator/>
      <MenuItem 
        Header="Select All"
        Name="cxmItemSelectAll"
        Click="ClickSelectAll" 
      />
      <MenuItem 
        Header="Select Current Line"
        Name="cxmItemSelectLine"
        Click="ClickSelectLine" 
      />
      <Separator/>
      <MenuItem 
        Header="Undo Last Action"
        Name="cxmItemUndo"
        Click="ClickUndo" 
      />
      <MenuItem 
        Header="Redo Last Action"
        Name="cxmItemRedo"
        Click="ClickRedo" 
      />
      <Separator/>
      <MenuItem 
        Header="Clear All Text"
        Name="cxmItemClear"
        Click="ClickClear" 
      />
    </ContextMenu>
  </TextBox.ContextMenu>
  This TextBox uses a simple custom context menu.  The context menu can be disabled by checking
  the CheckBox above, which simply sets the TextBox.ContextMenu property to null.
</TextBox>

Implement a Custom Context Menu

The following example shows the implementation code for the preceding context menu definition, as well as the code that enables and disables the context menu. The Opened event is used to dynamically enable or disable certain commands depending on the current state of the TextBox.

To restore the default context menu, use the ClearValue method to clear the value of the ContextMenu property. To disable the context menu altogether, set the ContextMenu property to a null reference (Nothing in Visual Basic).

private void MenuChange(Object sender, RoutedEventArgs ags)
{
    RadioButton rb = sender as RadioButton;
    if (rb == null || cxm == null) return;

    switch (rb.Name)
    {
        case "rbCustom":
            cxmTextBox.ContextMenu = cxm;
            break;
        case "rbDefault":
            // Clearing the value of the ContextMenu property
            // restores the default TextBox context menu.
            cxmTextBox.ClearValue(ContextMenuProperty);
            break;
        case "rbDisabled":
            // Setting the ContextMenu propety to
            // null disables the context menu.
            cxmTextBox.ContextMenu = null;
            break;
        default:
            break;
    }
}

void ClickPaste(Object sender, RoutedEventArgs args)     { cxmTextBox.Paste(); }
void ClickCopy(Object sender, RoutedEventArgs args)      { cxmTextBox.Copy(); }
void ClickCut(Object sender, RoutedEventArgs args)       { cxmTextBox.Cut(); }
void ClickSelectAll(Object sender, RoutedEventArgs args) { cxmTextBox.SelectAll(); }
void ClickClear(Object sender, RoutedEventArgs args)     { cxmTextBox.Clear(); }
void ClickUndo(Object sender, RoutedEventArgs args)      { cxmTextBox.Undo(); }
void ClickRedo(Object sender, RoutedEventArgs args)      { cxmTextBox.Redo(); }

void ClickSelectLine(Object sender, RoutedEventArgs args)
{
    int lineIndex = cxmTextBox.GetLineIndexFromCharacterIndex(cxmTextBox.CaretIndex);
    int lineStartingCharIndex = cxmTextBox.GetCharacterIndexFromLineIndex(lineIndex);
    int lineLength = cxmTextBox.GetLineLength(lineIndex);
    cxmTextBox.Select(lineStartingCharIndex, lineLength);
}

void CxmOpened(Object sender, RoutedEventArgs args)
{
    // Only allow copy/cut if something is selected to copy/cut.
    if (cxmTextBox.SelectedText == "")
        cxmItemCopy.IsEnabled = cxmItemCut.IsEnabled = false;
    else
        cxmItemCopy.IsEnabled = cxmItemCut.IsEnabled = true;

    // Only allow paste if there is text on the clipboard to paste.
    if (Clipboard.ContainsText())
        cxmItemPaste.IsEnabled = true;
    else
        cxmItemPaste.IsEnabled = false;
}
Private Sub MenuChange(ByVal sender As Object, ByVal args As RoutedEventArgs)
    Dim rb As RadioButton = CType(sender, RadioButton)
    If myGrid.Children.Contains(cxmTextBox) Then
        Select Case rb.Name
            Case "rbCustom"
                cxmTextBox.ContextMenu = cxm
            Case "rbDefault"
                'Clearing the value of the ContextMenu property
                'restores the default TextBox context menu.
                cxmTextBox.ClearValue(ContextMenuProperty)
            Case "rbDisabled"
                'Setting the ContextMenu propety to 
                'null disables the context menu.
                cxmTextBox.ContextMenu = Nothing
        End Select
    Else : Return
    End If
End Sub

Private Sub ClickPaste(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.Paste()
End Sub
Private Sub ClickCopy(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.Copy()
End Sub
Private Sub ClickCut(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.Cut()
End Sub
Private Sub ClickSelectAll(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.SelectAll()
End Sub
Private Sub ClickClear(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.Clear()
End Sub
Private Sub ClickUndo(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.Undo()
End Sub
Private Sub ClickRedo(ByVal sender As Object, ByVal args As RoutedEventArgs)
    cxmTextBox.Redo()
End Sub
Private Sub ClickSelectLine(ByVal sender As Object, ByVal args As RoutedEventArgs)
    Dim lineIndex As Integer = cxmTextBox.GetLineIndexFromCharacterIndex(cxmTextBox.CaretIndex)
    Dim lineStartingCharIndex As Integer = cxmTextBox.GetCharacterIndexFromLineIndex(lineIndex)
    Dim lineLength As Integer = cxmTextBox.GetLineLength(lineIndex)
    cxmTextBox.Select(lineStartingCharIndex, lineLength)
End Sub
Private Sub CxmOpened(ByVal sender As Object, ByVal args As RoutedEventArgs)
    'Only allow copy/cut if something is selected to copy/cut.
    If cxmTextBox.SelectedText = "" Then
        cxmItemCopy.IsEnabled = cxmItemCut.IsEnabled = False
    Else
        cxmItemCopy.IsEnabled = cxmItemCut.IsEnabled = True
        'Only allow paste if there is text on the clipboard to paste.
        If Clipboard.ContainsText() Then
            cxmItemPaste.IsEnabled = True
        Else
            cxmItemPaste.IsEnabled = False
        End If
    End If
End Sub

See also