Commands and Input Bindings
WPF separates what a menu item or button does from the control that triggers it: a command names the action, a command binding says what happens, and an input binding gives the action a key. The reference documents no commands of the TextControl, so the command bindings of your window call its methods, such as Copy, Undo or Print, and ask its Can... properties whether a command is available. The window built below has a menu, a toolbar and key bindings that all run through commands.
How Commands Reach the Editor
| Part | What it does | Where it goes |
|---|---|---|
RoutedUICommand | Names an action, with a text and optional key gestures | A static class, or the predefined ApplicationCommands |
CommandBinding | Connects a command to an Executed handler and a CanExecute handler | Window.CommandBindings |
KeyBinding | Connects a key to a command | Window.InputBindings |
Command property | Triggers the command | MenuItem, Button and other controls |
A routed command travels from the element with the keyboard focus, usually the TextControl, up to the window, where the command binding handles it. CanExecute is asked again whenever the input changes, and WPF disables every menu item and button of a command that can't execute.
The editor handles some keys itself: Ctrl+C, Ctrl+X, Ctrl+V, Ctrl+Z, Ctrl+Y and the others listed in Keyboard Shortcuts. The commands for these actions below have no key gesture, and their menu items show the key with InputGestureText only. Keys the editor doesn't use, such as Ctrl+O, Ctrl+S, Ctrl+P or Ctrl+B, can belong to your commands.
Define the Commands
ApplicationCommands of WPF already defines Open, Save and Print with the keys Ctrl+O, Ctrl+S and Ctrl+P. The other commands go into a class of your own, EditorCommands.cs:
using System.Windows.Input;
namespace MyWPFApp
{
public static class EditorCommands
{
// Ctrl+B is not a built-in key of the editor, so the command can own it
public static readonly RoutedUICommand Bold = new RoutedUICommand(
"Bold", "Bold", typeof(EditorCommands),
new InputGestureCollection { new KeyGesture(Key.B, ModifierKeys.Control) });
public static readonly RoutedUICommand InsertDate = new RoutedUICommand(
"Insert Date", "InsertDate", typeof(EditorCommands));
// The editor handles Ctrl+C, Ctrl+X, Ctrl+V, Ctrl+Z and Ctrl+Y itself,
// so these commands get no key gesture
public static readonly RoutedUICommand Cut = new RoutedUICommand("Cut", "Cut", typeof(EditorCommands));
public static readonly RoutedUICommand Copy = new RoutedUICommand("Copy", "Copy", typeof(EditorCommands));
public static readonly RoutedUICommand Paste = new RoutedUICommand("Paste", "Paste", typeof(EditorCommands));
public static readonly RoutedUICommand Undo = new RoutedUICommand("Undo", "Undo", typeof(EditorCommands));
public static readonly RoutedUICommand Redo = new RoutedUICommand("Redo", "Redo", typeof(EditorCommands));
}
}
Bind the Commands in XAML
MainWindow.xaml binds each command to its handlers, gives Insert Date the key F5 and uses the commands in a menu and a toolbar:
<Window x:Class="MyWPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:TXTextControl.WPF;assembly=TXTextControl.WPF"
xmlns:local="clr-namespace:MyWPFApp"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
<CommandBinding Command="ApplicationCommands.Print"
Executed="Print_Executed" CanExecute="Print_CanExecute" />
<CommandBinding Command="local:EditorCommands.Cut"
Executed="Cut_Executed" CanExecute="Copy_CanExecute" />
<CommandBinding Command="local:EditorCommands.Copy"
Executed="Copy_Executed" CanExecute="Copy_CanExecute" />
<CommandBinding Command="local:EditorCommands.Paste"
Executed="Paste_Executed" CanExecute="Paste_CanExecute" />
<CommandBinding Command="local:EditorCommands.Undo"
Executed="Undo_Executed" CanExecute="Undo_CanExecute" />
<CommandBinding Command="local:EditorCommands.Redo"
Executed="Redo_Executed" CanExecute="Redo_CanExecute" />
<CommandBinding Command="local:EditorCommands.Bold" Executed="Bold_Executed" />
<CommandBinding Command="local:EditorCommands.InsertDate" Executed="InsertDate_Executed" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="F5" Command="local:EditorCommands.InsertDate" />
</Window.InputBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="ApplicationCommands.Open" />
<MenuItem Command="ApplicationCommands.Save" />
<MenuItem Command="ApplicationCommands.Print" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Command="local:EditorCommands.Undo" InputGestureText="Ctrl+Z" />
<MenuItem Command="local:EditorCommands.Redo" InputGestureText="Ctrl+Y" />
<Separator />
<MenuItem Command="local:EditorCommands.Cut" InputGestureText="Ctrl+X" />
<MenuItem Command="local:EditorCommands.Copy" InputGestureText="Ctrl+C" />
<MenuItem Command="local:EditorCommands.Paste" InputGestureText="Ctrl+V" />
</MenuItem>
<MenuItem Header="F_ormat">
<MenuItem Command="local:EditorCommands.Bold" />
<MenuItem Command="local:EditorCommands.InsertDate" InputGestureText="F5" />
</MenuItem>
</Menu>
<ToolBar DockPanel.Dock="Top">
<Button Content="Save" Command="ApplicationCommands.Save" />
<Button Content="Undo" Command="local:EditorCommands.Undo" />
<Button Content="Bold" Command="local:EditorCommands.Bold" />
</ToolBar>
<WPF:TextControl x:Name="textControl1" />
</DockPanel>
</Window>
A MenuItem with a RoutedUICommand and no Header shows the text of the command, and for a command with a key gesture, such as Bold, the key as well.
Write the Handlers
The handlers in MainWindow.xaml.cs call the methods of the editor:
using System;
using System.Windows;
using System.Windows.Input;
using TXTextControl;
namespace MyWPFApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
// The built-in Open dialog box; the enumeration is written in full
// because Window has a DialogResult property of its own
if (textControl1.Load() == TXTextControl.WPF.DialogResult.OK)
{
Title = "Document opened";
}
}
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Save(StreamType.WordprocessingML);
}
private void Print_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = textControl1.CanPrint;
}
private void Print_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Print("Document", true);
}
private void Copy_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = textControl1.CanCopy;
}
private void Cut_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Cut();
}
private void Copy_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Copy();
}
private void Paste_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = textControl1.CanPaste;
}
private void Paste_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Paste();
}
private void Undo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = textControl1.CanUndo;
}
private void Undo_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Undo();
}
private void Redo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = textControl1.CanRedo;
}
private void Redo_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Redo();
}
private void Bold_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Selection.Bold = !textControl1.Selection.Bold;
}
private void InsertDate_Executed(object sender, ExecutedRoutedEventArgs e)
{
textControl1.Selection.Text = DateTime.Today.ToLongDateString();
}
}
}
The code works as follows:
- Load without parameters opens the built-in Open dialog box and returns a DialogResult of the namespace
TXTextControl.WPF. Inside aWindow, the nameDialogResultalone means the property of the window, so the enumeration is written with its namespace. - Save with a StreamType opens the Save As dialog box, which offers the file format filters of the given stream type, here DOCX. Load and Save Documents adds the current file and a save-changes prompt.
- Print opens the print dialog box;
truestarts it with the settings of the previous call. CanPrint depends on the document permissions and the edit mode. - CanCopy, CanPaste, CanUndo and CanRedo answer the
CanExecutequestions, and Cut, Copy, Paste, Undo and Redo do the work. - Selection is the selected text or the input position. Toggling Bold formats it, and setting
Textinserts the date there.
Run the application and select a word: Cut and Copy become available in the menu. Press Ctrl+B to make it bold, and F5 to insert today's date.
Commands From a View Model
In an MVVM application, the commands are ICommand properties of the view model, bound with Command="{Binding SaveCommand}", and a KeyBinding can bind to them as well:
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}" />
</Window.InputBindings>
The view model can't call textControl1 directly. MVVM Integration Patterns shows how its commands reach the editor through bindings and an interface that the window implements.
Keys Only While the Editor Has the Focus
Key bindings of the window work wherever the focus is in the window. A key that should act only while the user types in the document is handled in the KeyDown event of the TextControl instead, as Keyboard Shortcuts shows with Ctrl+F and Ctrl+H. The TX Words sample Shortcuts of the setup uses the same event for Ctrl+O, Ctrl+S and Ctrl+P.
Next Steps
- MVVM Integration Patterns: commands in a view model.
- Clipboard Operations: what
Cut,CopyandPastetransfer. - Undo and Redo: the undo history and grouped actions.
- Custom Tabs and Commands: commands in the ribbon.