- Mastering Windows Presentation Foundation
- Sheridan Yuen
- 917字
- 2021-06-24 16:49:02
Introducing the ICommand interface
When it comes to button clicks in WPF and MVVM, instead of handling the well-known Click event, we typically use some form of command that implements the ICommand interface. Let's take a look at an example of a basic standard command:
using System; using System.Windows.Forms; using System.Windows.Input; public class TestCommand : ICommand { public event EventHandler CanExecuteChanged; public void Execute(object parameter) { MessageBox.Show("You executed a command"); } public bool CanExecute(object parameter) { return true; } }
We can see that it has an Execute method, where the functionality that the command provides is performed. The CanExecute method is called by the CommandManager at various points over time, when it believes that the output value may have changed. We'll cover this in more detail later, but basically, raising the CanExecuteChanged event is one of the ways to trigger the CommandManager to do this. The output of the CanExecute method specifies whether the Execute method can be called or not.
You can imagine how cumbersome it would be if we had to create one of these classes for every action that we needed to implement. Furthermore, there is no context of where the command was called from other than the single command parameter. This means that if we wanted the command to add an item into a collection, we would have to put both the collection and the item to add into another object so that they could both be passed through the single input parameter.
When using MVVM, rather than implementing the commands in the standard way, we tend to use a single, reusable implementation that allows us to handle actions with standard methods directly in the View Model. This enables us to use commands without having to create a separate class for each one. There are a number of variations of this command, but its simplest form is shown here:
using System; using System.Windows.Input; public class ActionCommand : ICommand { private readonly Action<object> action; private readonly Predicate<object> canExecute; public ActionCommand(Action<object> action) : this(action, null) { } public ActionCommand(Action<object> action, Predicate<object> canExecute) { this.action = action; this.canExecute = canExecute; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public bool CanExecute(object parameter) { return canExecute == null ? true : canExecute(parameter); } public void Execute(object parameter) { action(parameter); } }
The action parameter of type Action<object> will hold the reference to the method that will be called when the command is executed and the object generic parameter relates to the optionally used command parameter. The canExecute parameter of type Predicate<object> will hold the reference to the method that will be called to verify whether the command can be executed or not and its object generic parameter relates to the optionally used command parameter again.
The CanExecuteChanged event should be raised whenever the canExecute parameter value changes. It is typically handled by command sources, such as the Button control, to set their IsEnabled property value appropriately. When a command source receives a notification that this event has been raised, it will call the ICommand.CanExecute method to check the new value. Therefore, when a command can execute, its data bound control will be enabled and when it can't, its data bound control will be disabled.
The CommandManager.RequerySuggested event will be raised when the CommandManager detects a change in the UI that could reflect on whether a command could execute or not. For example, this could be due to a user interaction, such as the selection of an item in a collection or some other change in focus. Therefore, connecting one to the other seems to be a logical thing to do. In fact, an example of this is actually found in the source code of the .NET RoutedCommand class.
This command class is typically used in the View Model classes, as shown in the following example, where the command functionality comes from the Save method and the bool return value of the CanSave method determines whether the command can execute or not:
public ICommand SaveCommand { get { return new ActionCommand(action => Save(), canExecute => CanSave()); } }
A safer way to ensure that the command is never called by code when the CanExecute condition is not satisfied would be to make this alteration; however, note that the CommandManager class will always perform this check before calling any commands anyway:
public void Execute(object parameter) { if (CanExecute(parameter)) action(parameter); }
Full credit for this custom command should go to Josh Smith, as his RelayCommand class was the first implementation like this that I came across, although there are several variations to be found online. The beauty of this particular implementation should not be underestimated. Not only is it simple, elegant, and saves us from writing large amounts of code, but it also makes testing our functionality much easier, as our command code can now be defined right in our View Models.
We'll look at this ActionCommand again and in more detail in Chapter 3, Writing Custom Application Frameworks, but for now, let's move on to the next method of communication.
- Microsoft Dynamics 365 Extensions Cookbook
- Magento 2 Theme Design(Second Edition)
- Getting Started with CreateJS
- 前端架構:從入門到微前端
- 深入理解Java7:核心技術與最佳實踐
- D3.js 4.x Data Visualization(Third Edition)
- Nginx Lua開發實戰
- QGIS Python Programming Cookbook(Second Edition)
- 編程改變生活:用Python提升你的能力(進階篇·微課視頻版)
- Oracle Data Guard 11gR2 Administration Beginner's Guide
- HTML5移動Web開發
- Isomorphic Go
- Learning Puppet Security
- IBM Cognos Insight
- Spark內核設計的藝術:架構設計與實現