|
|||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
PrefaceAs this article does not intend to be a beginner's article I presume you know what are WPF commands and what are they used for. But if you don't, I think this is the right time to learn about them. There are many good articles and books describing the nature and usage of WPF commands. One great article is by Richard Griffin which you can read on his blog here: WPF Commands a scenic tour part I. I also recommend reading Smart Routed Commands in WPF for learning advanced concepts in WPF commands by Josh Smith. IntroductionCommands in WPF can be defined and maintained in a static class. The commands need to be bind with the window whose controls use them. To bind the commands, events CanExecute and Executed events need to defined in the window itself. But most of the times, people want all their commands and related CanExecute and Executed events in one single static class which makes it easier to maintain the program logic from one central place. The purpose of this article is to tell you how to enable this facility.Defining the commandsLet's define some commands which we will be using for this example. public class MyAppCommands { private static RoutedUICommand _AddContact; private static RoutedUICommand _EditContact; private static RoutedUICommand _DeleteContact; static MyAppCommands() { _AddContact = new RoutedUICommand("Add a new contact", "AddContact", typeof(MyAppCommands)); _EditContact = new RoutedUICommand("Edit an existing contact", "EditContact", typeof(MyAppCommands)); _DeleteContact = new RoutedUICommand("Delete an existing contact", "DeleteContact", typeof(MyAppCommands)); } // Command: AddContact public static RoutedUICommand AddContact { get { return _AddContact; } } // Command: EditContact public static RoutedUICommand EditContact { get { return _EditContact; } } // Command: DeleteContact public static RoutedUICommand DeleteContact { get { return _DeleteContact; } } } Now let us assume that we are using these three commands from a window which uses three buttons to fire the three commands respectively. <Window x:Class="CentralCommands.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Commands="clr-namespace:CentralCommands" Title="MainWindow" Height="300" Width="300" x:Name="mainWindow"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Button HorizontalAlignment="Stretch" Margin="20" Name="btnAdd" VerticalAlignment="Stretch" Grid.Row="0" Command="Commands:MyAppCommands.AddContact">Add Contact</Button> <Button HorizontalAlignment="Stretch" Margin="20" Name="btnEdit" VerticalAlignment="Stretch" Grid.Row="1" Command="Commands:MyAppCommands.EditContact">Edit Contact</Button> <Button HorizontalAlignment="Stretch" Margin="20" Name="btnDelete" VerticalAlignment="Stretch" Grid.Row="2" Command="Commands:MyAppCommands.DeleteContact">Save Contact</Button> </Grid> </Window> As of now, if you execute this code, all the buttons will be disabled. The reason is that we have not bound CanExecute and/or Executed event for the commands. If only CanExecute is defined, the button will only be enabled when CanExecute event sets CanExecute property of the CanExecuteRoutedEventArgs parameter passed to the method to true but nothing will happen if you will click the button. If Executed is defined for a command, the Executed method will be called when you click the button. If you define only Executed, the button using the command will be enabled, no matter what. Let's define the CanExecute and Executed event for the commands in our MyAppCommands static class. The commands do nothing other than displaying a message box about what command was executed. AddContact and DeleteContact are enabled and EditContact is disabled for testing purposes. public static void AddContact_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Add contact command executed"); } public static void AddContact_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } public static void EditContact_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Edit contact command executed"); } public static void EditContact_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = false; } public static void DeleteContact_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Delete contact command executed"); } public static void DeleteContact_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } The problemNow we need to bind these events with the commands in the command bindings of the window. Let's try to define the events using the x:Static markup extension which binds any static by-value entity to the source (we only bind AddContact command for the time being): <Window.CommandBindings> <CommandBinding Command="Commands:MyAppCommands.AddContact" CanExecute="{x:Static Commands:MyAppCommands.AddContact_CanExecute}" Executed="{x:Static Commands:MyAppCommands.AddContact_Executed}"/> </Window.CommandBindings> If you will try to compile this code you will get the following error: Why are we getting the error? The event is defined as static in the MyAppCommand class but we still cannot bind the event to the command. The reason is, the current WPF version's XAML does not allow us to bind event handlers in this way. The event handlers must be defined in the code behind file inside the MainWindow class. I don't know if this is a bug, a accidently left out feature, or we are not even supposed to use this functionality but this stops us from defining a centralized location for handling all command's Executed and CanExecute events. The solutionThe solution to this problem is to use code instead of XAML to define the bindings. Let us make a static function which will bind the commands and their respected events to the MainWindow window. public static void BindCommandsToWindow(Window window) { window.CommandBindings.Add( new CommandBinding(AddContact, AddContact_Executed, AddContact_CanExecute)); window.CommandBindings.Add( new CommandBinding(EditContact, EditContact_Executed, EditContact_CanExecute)); window.CommandBindings.Add( new CommandBinding(DeleteContact, DeleteContact_Executed, DeleteContact_CanExecute)); } We need to call this method from MainWindow.Loaded event, passing the MainWindow instance: void MainWindow_Loaded(object sender, RoutedEventArgs e) { MyAppCommands.BindCommandsToWindow(this); } Now try to run and execute the code.
Voila!! It works. A small tipYou can send the command a parameter by using the CommandParameter dependency property of the button. As the data is sent as an object, you can send almost anything you want. The parameter is sent to both CanExecute and Executed event. Let us suppose we want to set the status of Edit's CanExecute using code so that when Add is clicked, Edit is available and when delete is clicked, it's not. For clarity, I am making a separate class named CommandParameter which defines a bool property CanEditBeExecuted, although it can be done without defining a separate class at all. We also define a parameter property of a CommandParameter type and set CanEditBeExecuted to false in the constructor of MainWindow. public class CommandParameter { public bool CanEditBeExecuted { get; set; } } /// Now define the CommandParameter for the buttons <Button HorizontalAlignment="Stretch" Margin="20" Name="btnAdd" VerticalAlignment="Stretch" Grid.Row="0" Command="Commands:MyAppCommands.AddContact" CommandParameter="{Binding ElementName=mainWindow, Path=parameter}">Add Contact</Button> <Button HorizontalAlignment="Stretch" Margin="20" Name="btnEdit" VerticalAlignment="Stretch" Grid.Row="1" Command="Commands:MyAppCommands.EditContact" CommandParameter="{Binding ElementName=mainWindow, Path=parameter}">Edit Contact</Button> <Button HorizontalAlignment="Stretch" Margin="20" Name="btnDelete" VerticalAlignment="Stretch" Grid.Row="2" Command="Commands:MyAppCommands.DeleteContact" CommandParameter="{Binding ElementName=mainWindow, Path=parameter}">Save Contact</Button> We also change the command's Execute and CanExecute handlers to reflect our strategy. public static void AddContact_Executed(object sender, ExecutedRoutedEventArgs e) { CommandParameter parameter = e.Parameter as CommandParameter; if (parameter != null) parameter.CanEditBeExecuted = true; MessageBox.Show("Add contact command executed"); } public static void AddContact_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } public static void EditContact_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Edit contact command executed"); } public static void EditContact_CanExecute(object sender, CanExecuteRoutedEventArgs e) { CommandParameter parameter = e.Parameter as CommandParameter; if (parameter != null) e.CanExecute = parameter.CanEditBeExecuted; else e.CanExecute = false; } public static void DeleteContact_Executed(object sender, ExecutedRoutedEventArgs e) { CommandParameter parameter = e.Parameter as CommandParameter; if (parameter != null) parameter.CanEditBeExecuted = false; MessageBox.Show("Delete contact command executed"); } public static void DeleteContact_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } If you will execute this code now, you will notice that the edit button does not reflect the changes made to the CanEditBeExecuted property. If you will search the net, the normal answer you will find is that this happens because the property cannot notify the button when is it changed. The solution is to derive CommandParameter from INotifyPropertyChanged and raise the property changed event when CanEditBeExecuted is changed. The answer is NO. All you need to do is this: you need to instantiate the parameter object BEFORE InitializeComponent and the edit button will reflect the changes. So, if you change the constructor in the following way, edit button will reflect the changes made to CanEditBeExecuted. public MainWindow() { parameter = new CommandParameter(); InitializeComponent(); Loaded += new RoutedEventHandler(MainWindow_Loaded); parameter.CanEditBeExecuted = false; } I just mentioned this tip because I think many people make this same mistake and the solutions available on the net regarding the matter are mostly misleading. Although implementing INotifyPropertyChanged is a good idea but implementing it just because of the above mentioned reasons, is not. ConclusionI hope people who want a centralized static class to handle all the command functionality at one single place will find this article useful. Please feel free to comment or point out mistakes.
|
||||||||||||||||||||||||||||||||||||||||