Learning how to communicate again
As we tend not to handle UI events directly, when using MVVM, we need alternative ways to implement the same functionality that they provide. Different methods are required to reproduce the functionality of different events. For example, the functionality of the SelectionChanged
event of a collection control is typically reproduced by data binding a View Model property to the SelectedItem
property of the collection control.
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding CurrentItem}" />
In this example, the setter of the CurrentItem
property will get called by the WPF Framework each time a new item is selected from the ListBox
. Therefore, instead of handling the SelectionChanged
event in the code behind, we can call any method directly from the property setter in the View Model:
public TypeOfObject CurrentItem { get { return currentItem; } set { currentItem = value; DoSomethingWithTheNewlySelectedItem(currentItem); } }
Note that we need to keep any methods that we call from data bound property setters from doing too much, as the time that it takes to execute them could negatively affect the performance when entering data. However, in this example, we would typically use this method to start an asynchronous data access function using a value from the current item or alter the value of another property in the View Model.
Many other UI events can be replaced with some form of Trigger
in the XAML markup directly. For example, imagine that we had an Image
element that was set as the Content
property value of a Button
control and that we wanted the Image
element to be semi-transparent when the Button
was disabled. Instead of handling the UIElement.IsEnabledChanged
event in the code behind file, we could write a DataTrigger
in a Style
that we could apply to the Image
element:
<Style x:Key="ImageInButtonStyle" TargetType="{x:Type Image}"> <Setter Property="Opacity" Value="1.0" /> <Style.Triggers> <DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}, FallbackValue=False}" Value="False"> <Setter Property="Opacity" Value="0.5" /> </DataTrigger> </Style.Triggers> </Style>
Binding syntax will be covered in detail in Chapter 4, Becoming Proficient With Data Binding, but in short, the binding in this DataTrigger
is specifying the target as the IsEnabled
property of the ancestor (or parent) of the Image
element with a type of Button
. When this binding target has a value of False
, the Opacity
property of the Image
will be set to 0.5
and set back to its original value when the target property value is True
. Therefore, the Image
element in our Button
will become semi-transparent when the Button
is disabled.
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; } }
Tip
Please note that in this book, we will display code with two-space tabs, instead of the more commonly used four-space tabs, in order to enable more characters of each code snippet to fit onto each line.
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); } }
This 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, please note that the CommandManager
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.
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. 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.
Handling events in Attached Properties
There is one way to handle events in WPF without having to resort to writing code in the code behind file of a View. Using Attached Properties, we can encapsulate the handling of events and effectively expose their behavior using properties that we can data bind to in our Views. Let's take a look at a simple example using the PreviewKeyDown
event:
public static DependencyProperty OnEnterKeyDownProperty = DependencyProperty.RegisterAttached("OnEnterKeyDown", typeof(ICommand), typeof(TextBoxProperties), new PropertyMetadata(OnOnEnterKeyDownChanged)); public static ICommand GetOnEnterKeyDown(DependencyObject dependencyObject) { return (ICommand)dependencyObject.GetValue(OnEnterKeyDownProperty); } public static void SetOnEnterKeyDown(DependencyObject dependencyObject, ICommand value) { dependencyObject.SetValue(OnEnterKeyDownProperty, value); } public static void OnOnEnterKeyDownChanged( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { TextBox textBox = (TextBox)dependencyObject; if (e.OldValue == null && e.NewValue != null) textBox.PreviewKeyDown += TextBox_OnEnterKeyDown; else if (e.OldValue != null && e.NewValue == null) textBox.PreviewKeyDown -= TextBox_OnEnterKeyDown; } private static void TextBox_OnEnterKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter || e.Key == Key.Return) { TextBox textBox = sender as TextBox; ICommand command = GetOnEnterKeyDown(textBox); if (command != null && command.CanExecute(textBox)) command.Execute(textBox); } }
As can be seen in this example, the event is handled by attaching an event handler in the normal way, except that all relating code is encapsulated within the class that declares the Attached Property. Let's take a closer look. First, we declare an Attached Property named OnEnterKeyDown
of type ICommand
in a class named TextBoxProperties
, and we pass a reference of our handling method to the PropertyChangedCallback
delegate parameter of the PropertyMetadata
constructor.
The GetOnEnterKeyDown
and SetOnEnterKeyDown
methods represent the normal way to get and set Attached Property values. In the unfortunately named OnOnEnterKeyDownChanged
method, which will be called when the property value changes, we look at the NewValue
and OldValue
values of the DependencyPropertyChangedEventArgs
input parameter in order to decide whether we need to attach or detach the event handler to the 'PreviewKeyDown
event of the relevant TextBox
.
If the OldValue
value is null and the NewValue
value is not null, it means that there was no previous value, and so the property is being set for the first time. In this case, we want to attach the event handler. Conversely, when the OldValue
value is not null and the NewValue
value is null, it means that there previously was a value, which has been removed, so we should detach the event handler.
Finally, the TextBox_OnEnterKeyDown
event handling method first detects whether either the
Enter
key or the
Return
key were pressed. If one was pressed, the data bound ICommand
instance is checked for null
and if the command can execute, it is then executed. Therefore, we have effectively wrapped a PreviewKeyDown
event in an Attached Property and can now execute any command that has been data bound to it when the user presses
Enter
on their keyboard.
In order to use this Attached Property, we must first add an XML namespace prefix for our Attached
folder in the XAML file of the View that this functionality is required in. Note that the TextBoxProperties
class will be declared in the Attached
folder of the Views project and so, its namespace will be as follows:
xmlns:Attached="clr-namespace:CompanyName.ApplicationName.Views.Attached; assembly=CompanyName.ApplicationName.Views"
Microsoft's convention for naming these prefixes is for the first character to be a lowercase letter, but it has always made more sense to me to simply use the last segment of the declared namespace, which will start with a capital letter. Once you have defined the prefix, you can use the Attached Property, as shown in the following example:
<TextBox Attached:TextBoxProperties.OnEnterKeyDown="{Binding Command}" />
Any UI events that we might need to handle in our applications can be encapsulated in Attached Properties in this same way. At first, this might seem to be a complicated way to handle events, compared with having a simple handler in a code behind file, but once we have a collection of these properties declared, we will find ourselves having to create fewer and fewer new ones. Think of them as simply being a reusable way of converting events into properties.
Making use of delegates
Delegates are very similar to events and, in fact, events can be thought of as a particular kind of delegate. They are a very simple tool to use to pass a signal or message from one place to another in the program. Unlike when creating custom events, we are not forced to use particular input parameters, for example, some form of the EventArgs
class. We are totally unconstrained when creating custom delegates and are able to define our own method signatures, including both input and output parameter types.
As most of you will already be familiar with events and event handling, you'll already inadvertently know how to use delegates too. Let's look at a simple example. Imagine that we have a parent View Model that spawns child View Models and that one of these child View Models is paired with a View that enables administrative users to select security permissions. Now, let's imagine that the parent View that relates to the parent View Model has a menu that needs to be updated depending on the user's selection in the child View. How do we notify the parent View Model upon selection?
This is where delegates save the day. Keeping this example simple initially, let's say that we just need to notify the parent View Model that a particular change has been made so that it can refresh the current user's security permissions from a database. In this case, we only need to pass a signal, so we can create a delegate with no input or output parameters. We can declare it in the View Model that will be sending the signal, in this case, the child View Model.
public delegate void Signal();
Note that we define it in the same way that we define an abstract method, except that the abstract
keyword is replaced with the delegate
keyword after the access modifier. In short, a delegate defines a type that references a method with a particular signature. Now that we have defined our signaling delegate, we need to create a way for elements outside the View Model to use it. For this, we can simply create a property of the type of our new delegate in the same View Model:
public Signal OnSecurityPermissionChanged { get; set; }
As we don't need any property change notifications for this property, we can save ourselves some typing and take advantage of the .NET Auto-Implemented Property syntax. Bear in mind that delegates work in a multicast way like events, meaning that we can attach more than one handler to each one. In order to do this, we need to use the +=
operator to add handlers for the delegate, and in this example, we would want to do that in the parent View Model when the child View is instantiated:
ChildViewModel viewModel = new ChildViewModel(); viewModel.OnSecurityPermissionChanged += RefreshSecurityPermissions;
Here, we have assigned the RefreshSecurityPermissions
method in the parent View Model to be the handler for this delegate. Note that we omit the parenthesis and the input parameters if there were any when attaching the handler. Now, you may be wondering, "What does the method signature of this handler look like?", but you already have the answer to this. If you remember, we declared the delegate with the signature of the method that we want to handle it. Therefore, any method that shares this signature can be a handler for this type of delegate:
private void RefreshSecurityPermissions() { // Refresh user's security permissions when alerted by the signal }
Note that the name used is irrelevant and all that matters when matching the delegate signature are the input and output parameters. So, we now have our delegate declared and hooked up to a handler in the parent View Model, but it's still not going to do anything because we haven't actually called it yet. In our example, it's the child View Model that is going to call the delegate because that's the object that needs to send out the information, or signal in this case.
When calling delegates, we must always remember to check for null
before trying to use them because there may be no handlers attached. In our example, we'd call our Signal
delegate via the OnSecurityPermissionChanged
property at the point that we need to send the signal from the child View Model, let's say in reaction to a user changing their own security permissions:
if (OnSecurityPermissionChanged != null) OnSecurityPermissionChanged();
Alternatively, we could do so using the more concise null conditional operator in C# version 6.0, which calls the delegate's Invoke
method if it is not null:
OnSecurityPermissionChanged?.Invoke();
Note that we do need to include the parenthesis when calling the delegate in the first example even though OnSecurityPermissionChanged
is a property. This is because the delegate type of the property relates to a method and it is that method that we are calling. Please bear in mind that the first of these methods is not thread safe, so if thread safety is important for your application, then you will need to use the latter way.
We now have the complete picture, but while it is common to have a signal-sending delegate such as this, it is not overly useful because it only passes a signal with no other information. In many real-world scenarios, we would typically want to have some sort of input parameter so that we could pass some information, rather than just a signal.
For example, if we wanted to be notified with details each time a user selected a different item from a collection control in the UI, we could add a CurrentItem
property into a generic BaseCollection
class in our application and data bind it to the data bound collection control's SelectedItem
property. This CurrentItem
property would then be called by the WPF Framework each time a user makes a new selection, and so we can call our new delegate from its property setter:
protected T currentItem; public virtual CurrentItemChange CurrentItemChanged { get; set; } public virtual T CurrentItem { get { return currentItem; } set { T oldCurrentItem = currentItem; currentItem = value; CurrentItemChanged?.Invoke(oldCurrentItem, currentItem); NotifyPropertyChanged(); } }
Delegates can be used to communicate between any related classes as long as they have access to the class that exposes the delegate so that they can attach a handler. They are commonly used to send information between child Views or View Models and their parents, or even between Views and View Models, but they can also be used to pass data between any two connected parts of the application.