Daily Archives: April 15, 2014
WPF Commands (part 2)
Let’s start with a program that uses Cut, Copy and Paste in two TextBoxes without writing any C# code. This is not a typo. We only need XAML for this.
<Window x:Class="DemoApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="400" Width="400"> <DockPanel LastChildFill="True" > <Menu DockPanel.Dock="Top"> <MenuItem Header="_Edit"> <MenuItem Command="{x:Static ApplicationCommands.Cut}" CommandParameter="Cut it!"/> <MenuItem Command="{x:Static ApplicationCommands.Copy}" CommandParameter="Copy it!"/> <MenuItem Command="{x:Static ApplicationCommands.Paste}" CommandParameter="Paste it!"/> </MenuItem> </Menu> <ToolBarTray Background="Gray" DockPanel.Dock="Top"> <ToolBar Band="0" BandIndex="0" > <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> </ToolBar> <ToolBar Band="1" BandIndex="1"> <ToolBarPanel Orientation="Vertical"> <Label Content="Dummy0" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy1" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy2" ToolBar.OverflowMode="AsNeeded" /> </ToolBarPanel> </ToolBar> </ToolBarTray> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="0" Grid.ColumnSpan="1" Margin="0" >It happened that a Fox caught its tail in a trap, and in struggling to release himself lost all of it but the stump. At first he was ashamed to show himself among his fellow foxes.</TextBox> <GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,0,0,0" Width="3"/> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="2" Grid.ColumnSpan="1" Margin="0" >But at last he determined to put a bolder face upon his misfortune, and summoned all the foxes to a general meeting to consider a proposal which he had to place before them.</TextBox> </Grid> </DockPanel> </Window>
I added a Toolbar with some dummy labels just to keep the learning curve going. You remove them without any risk.
What is happening here?
Some input controls handle command events on their own. Everything is built-in already. All you need to do is to provide the Buttons or MenuItems which call these commands. The elements even enable/disable themselves. We have two textboxes in the example. These commands are applied to the element that has the focus.
How can this be achieved? The element finds the window instance and then determines what element was focused previously. This only works for Toolbars and Menus UNLESS you set the CommandTarget property manually.
Let’s add standard buttons now. You cannot see any effect when you press them. The buttons are even ghosted. To solve this we assign the names TextBox1 and TextBox2 and link the Button CommandTargets to these elements.
You can now cut or copy from TextBox1 and paste it into TextBox2.
<Window x:Class="DemoApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="400" Width="400"> <DockPanel LastChildFill="True" > <Menu DockPanel.Dock="Top"> <MenuItem Header="_Edit"> <MenuItem Command="{x:Static ApplicationCommands.Cut}" CommandParameter="Cut"/> <MenuItem Command="{x:Static ApplicationCommands.Copy}" CommandParameter="Copy"/> <MenuItem Command="{x:Static ApplicationCommands.Paste}" CommandParameter="Paste"/> </MenuItem> </Menu> <ToolBarTray Background="Gray" DockPanel.Dock="Top"> <ToolBar Band="0" BandIndex="0" > <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> </ToolBar> <ToolBar Band="1" BandIndex="1"> <ToolBarPanel Orientation="Vertical"> <Label Content="Dummy0" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy1" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy2" ToolBar.OverflowMode="AsNeeded" /> </ToolBarPanel> </ToolBar> </ToolBarTray> <!-- changed --> <StackPanel Orientation="Horizontal" DockPanel.Dock="Top"> <Button Command="{x:Static ApplicationCommands.Cut}" CommandTarget="{Binding ElementName=TextBox1}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" CommandTarget="{Binding ElementName=TextBox1}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" CommandTarget="{Binding ElementName=TextBox2}" Content="Paste" /> </StackPanel> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <!-- changed --> <TextBox Name="TextBox1" TextWrapping="Wrap" Width="Auto" Grid.Column="0" Grid.ColumnSpan="1" Margin="0" >It happened that a Fox caught its tail in a trap, and in struggling to release himself lost all of it but the stump. At first he was ashamed to show himself among his fellow foxes.</TextBox> <GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,0,0,0" Width="3"/> <!-- changed --> <TextBox Name="TextBox2" TextWrapping="Wrap" Width="Auto" Grid.Column="2" Grid.ColumnSpan="1" Margin="0" >But at last he determined to put a bolder face upon his misfortune, and summoned all the foxes to a general meeting to consider a proposal which he had to place before them.</TextBox> </Grid> </DockPanel> </Window>
But hardcoding is a really bad approach. Therefore we are going to use FocusManager.IsFocusScope=”True” instead. WPF then checks the parent focus. By default, the Window class is a focus scope as are the Menu, ContextMenu, and ToolBar classes.
The following example is flawless.
<Window x:Class="DemoApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="400" Width="400"> <DockPanel LastChildFill="True" > <Menu DockPanel.Dock="Top"> <MenuItem Header="_Edit"> <MenuItem Command="{x:Static ApplicationCommands.Cut}" CommandParameter="Cut"/> <MenuItem Command="{x:Static ApplicationCommands.Copy}" CommandParameter="Copy"/> <MenuItem Command="{x:Static ApplicationCommands.Paste}" CommandParameter="Paste"/> </MenuItem> </Menu> <ToolBarTray Background="Gray" DockPanel.Dock="Top"> <ToolBar Band="0" BandIndex="0" > <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> </ToolBar> <ToolBar Band="1" BandIndex="1"> <ToolBarPanel Orientation="Vertical"> <Label Content="Dummy0" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy1" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy2" ToolBar.OverflowMode="AsNeeded" /> </ToolBarPanel> </ToolBar> </ToolBarTray> <!-- changed --> <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" FocusManager.IsFocusScope="True"> <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> </StackPanel> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="0" Grid.ColumnSpan="1" Margin="0" >It happened that a Fox caught its tail in a trap, and in struggling to release himself lost all of it but the stump. At first he was ashamed to show himself among his fellow foxes.</TextBox> <GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,0,0,0" Width="3"/> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="2" Grid.ColumnSpan="1" Margin="0" >But at last he determined to put a bolder face upon his misfortune, and summoned all the foxes to a general meeting to consider a proposal which he had to place before them.</TextBox> </Grid> </DockPanel> </Window>
The IsFocusScope approach has the advantage that the same commands apply to several controls.
Custom Commands
We are going to write our own commands now. For this we need to create a class and add a property that returns a RoutedUICommand instance. This property needs to be static. And to initialize this class you also need a static constructor.
using System.Windows.Input; namespace CustomCommands { public class PlaySound { static PlaySound() { KeyGesture lShortCut = new KeyGesture(Key.P, ModifierKeys.Control, "Ctrl+p"); InputGestureCollection InputGestureCollection = new InputGestureCollection(); InputGestureCollection.Add(lShortCut); PlaySoundCommand = new RoutedUICommand("Play", "PlaySound", typeof(PlaySound), InputGestureCollection); } // static constructor public static RoutedUICommand PlaySoundCommand { get; private set; } } // class } // namespace
The MainWindow class should look like this. The method CommandBinding_PlaySound_Executed plays the system beep sound.
using System.Windows; using System.Windows.Input; namespace DemoApp { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void CommandBinding_PlaySound_Executed(object sender, ExecutedRoutedEventArgs e) { System.Media.SystemSounds.Beep.Play(); MessageBox.Show("Source: " + e.Source.ToString() + Environment.NewLine + "OriginalSource: " + e.OriginalSource.ToString() + Environment.NewLine + "Parameter: " + e.Parameter.ToString()); } // } // class } // namespace
Add the class in your XAML namespace. I used xmlns:c=”clr-namespace:CustomCommands” .
<Window x:Class="DemoApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:CustomCommands" Title="MainWindow" Height="400" Width="400"> <!-- above was changed --> <Window.CommandBindings> <!-- changed --> <CommandBinding Command="c:PlaySound.PlaySoundCommand" Executed="CommandBinding_PlaySound_Executed" /> </Window.CommandBindings> <DockPanel LastChildFill="True" > <Menu DockPanel.Dock="Top"> <MenuItem Header="_Edit"> <MenuItem Command="{x:Static ApplicationCommands.Cut}" CommandParameter="Cut"/> <MenuItem Command="{x:Static ApplicationCommands.Copy}" CommandParameter="Copy"/> <MenuItem Command="{x:Static ApplicationCommands.Paste}" CommandParameter="Paste"/> </MenuItem> <MenuItem Header="_Media"> <MenuItem Command="c:PlaySound.PlaySoundCommand" CommandParameter="Play"/> </MenuItem> </Menu> <ToolBarTray Background="Gray" DockPanel.Dock="Top"> <ToolBar Band="0" BandIndex="0" > <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> </ToolBar> <ToolBar Band="1" BandIndex="1"> <ToolBarPanel Orientation="Vertical"> <Label Content="Dummy0" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy1" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy2" ToolBar.OverflowMode="AsNeeded" /> </ToolBarPanel> </ToolBar> </ToolBarTray> <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" FocusManager.IsFocusScope="True"> <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> <!-- changed --> <Button Command="c:PlaySound.PlaySoundCommand" Content="Play" CommandParameter="What a lovely song!" /> </StackPanel> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="0" Grid.ColumnSpan="1" Margin="0" >It happened that a Fox caught its tail in a trap, and in struggling to release himself lost all of it but the stump. At first he was ashamed to show himself among his fellow foxes.</TextBox> <GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,0,0,0" Width="3"/> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="2" Grid.ColumnSpan="1" Margin="0" >But at last he determined to put a bolder face upon his misfortune, and summoned all the foxes to a general meeting to consider a proposal which he had to place before them.</TextBox> </Grid> </DockPanel> </Window>
There is a shortcut to calling commands. You can create an ICommand instance and provide it via a property. The downside – what did you expect? – is that you have no shortcut key or any other comfort.
Step 1: Create a class that inherits from interface ICommand.
using System; using System.Windows.Input; namespace CustomCommands { public class PlaySound2 : ICommand { object _DependencyObject; public PlaySound2(object xDependencyObject) { _DependencyObject = xDependencyObject; } // constructor public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } // public bool CanExecute(object xParameter) { return (DateTime.Now.Second % 2 == 0); // timer based example } // public void Execute(object xParameter) { //_DependencyObject.DoSomething(); System.Windows.MessageBox.Show("Parameter: " + xParameter.ToString()); System.Media.SystemSounds.Beep.Play(); } // } // class } // namespace
Step 2: instantiate that class and provide it via a property. You do not need to expose the class in your MainWindow. You can use any class. Set the DataContext to your class where the property is (or use a precise path that leads to that object).
using CustomCommands; using System; using System.Windows; using System.Windows.Input; namespace DemoApp { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); SimpleExecution = new PlaySound2("dummy"); DataContext = this; } private void CommandBinding_PlaySound_Executed(object sender, ExecutedRoutedEventArgs e) { System.Media.SystemSounds.Beep.Play(); MessageBox.Show("Source: " + e.Source.ToString() + Environment.NewLine + "OriginalSource: " + e.OriginalSource.ToString() + Environment.NewLine + "Parameter: " + e.Parameter.ToString()); } // public ICommand SimpleExecution { get; private set; } } // class } // namespace
Step 3: Bind the command in XAML.
<Window x:Class="DemoApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:CustomCommands" Title="MainWindow" Height="400" Width="400"> <Window.CommandBindings> <CommandBinding Command="c:PlaySound.PlaySoundCommand" Executed="CommandBinding_PlaySound_Executed" /> </Window.CommandBindings> <DockPanel LastChildFill="True" > <Menu DockPanel.Dock="Top"> <MenuItem Header="_Edit"> <MenuItem Command="{x:Static ApplicationCommands.Cut}" CommandParameter="Cut"/> <MenuItem Command="{x:Static ApplicationCommands.Copy}" CommandParameter="Copy"/> <MenuItem Command="{x:Static ApplicationCommands.Paste}" CommandParameter="Paste"/> </MenuItem> <MenuItem Header="_Media"> <MenuItem Command="c:PlaySound.PlaySoundCommand" CommandParameter="Play"/> </MenuItem> </Menu> <ToolBarTray Background="Gray" DockPanel.Dock="Top"> <ToolBar Band="0" BandIndex="0" > <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> </ToolBar> <ToolBar Band="1" BandIndex="1"> <ToolBarPanel Orientation="Vertical"> <Label Content="Dummy0" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy1" ToolBar.OverflowMode="AsNeeded" /> <Label Content="Dummy2" ToolBar.OverflowMode="AsNeeded" /> </ToolBarPanel> </ToolBar> </ToolBarTray> <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" FocusManager.IsFocusScope="True"> <Button Command="{x:Static ApplicationCommands.Cut}" Content="Cut" /> <Button Command="{x:Static ApplicationCommands.Copy}" Content="Copy" /> <Button Command="{x:Static ApplicationCommands.Paste}" Content="Paste" /> <Button Command="c:PlaySound.PlaySoundCommand" Content="Play" CommandParameter="What a lovely song!" /> <!-- changed --> <Button Command="{Binding SimpleExecution}" Content="StraightForward" CommandParameter="The German way!" /> </StackPanel> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="0" Grid.ColumnSpan="1" Margin="0" >It happened that a Fox caught its tail in a trap, and in struggling to release himself lost all of it but the stump. At first he was ashamed to show himself among his fellow foxes.</TextBox> <GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="1,0,0,0" Width="3"/> <TextBox TextWrapping="Wrap" Width="Auto" Grid.Column="2" Grid.ColumnSpan="1" Margin="0" >But at last he determined to put a bolder face upon his misfortune, and summoned all the foxes to a general meeting to consider a proposal which he had to place before them.</TextBox> </Grid> </DockPanel> </Window>
That’s it for today 🙂