- 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.
- 嵌入式軟件系統測試:基于形式化方法的自動化測試解決方案
- 案例式C語言程序設計
- GraphQL學習指南
- VSTO開發入門教程
- 機械工程師Python編程:入門、實戰與進階
- Learning Neo4j 3.x(Second Edition)
- WordPress Plugin Development Cookbook(Second Edition)
- Apex Design Patterns
- 程序是怎樣跑起來的(第3版)
- Hands-On Natural Language Processing with Python
- 微信小程序開發與實戰(微課版)
- 區塊鏈技術進階與實戰(第2版)
- ASP.NET程序開發范例寶典
- Tableau Desktop可視化高級應用
- Hands-On Robotics Programming with C++