Commanding: Binding Controls to Methods
A Command in WPF encapsulates a method to do something, a check on whether it is possible to do it at the moment , and a notification of when that state changes. It does this in a way so that one can bind a control like a Button or MenuItem to a command, and the control will execute the command when clicked and enable and disable itself according to whether the command is available.
WPF supports this infrastructure through two APIs: CommandBindings, which can be defined in XAML, but can only call methods defined in the XAML’s code-behind file, and the ICommand-Interface, which can be implemented in ViewModel classes, and supports creating properties in the ViewModel which allow to bind a control to a command.
For the first approach, I don’t like that I have to write code in the code-behind file. Defining a command is a routine thing, so I’d like to do it in XAML.
For the second approach, I don’t like that I have to implement an interface, and create kind of standard boilerplate glue-code in order to be able to make the UI call a method in the model. If possible, I want to do all the gluing that is necessary in XAML only.
What would an ideal solution look like? I’d like to have a way to just take any normal method in the ViewModel, bind a command to it (that would be a RoutedCommand object, like with the CommandBinding approach), and assign the RoutedCommand to any Control that supports commands.
In the post about the Sticky Command Design Pattern, I have already demonstrated how one can set up CommandBindings through attached properties. Now, I want to generalize this approach into a commanding framework.
A Reusable Sticky Command
The first element is a reusable component which ties a RoutedCommand and its implementation together. Basically, it contains the same information as a CommandBinding, and in fact it is just a wrapper and adapter which is responsible for creating CommandBindings while overcoming their limitations.
So, on the one hand, the Command class defines the usual Attach and Detach methods for Sticky Components:
If SupportsCanExecute Or UseValidation Then
StickyComponentManager.Attach(sender, Command, AddressOf OnExecuted, AddressOf OnCanExecute, Preview)
Else
StickyComponentManager.Attach(sender, Command, AddressOf OnExecuted, Nothing, Preview)
End If
End Sub
Protected Overrides Sub Detach(ByVal sender As Object)
StickyComponentManager.Detach(sender, Command)
End Sub
Command here is a property which holds a RoutedCommand and which can be configured through XAML. UseValidation and Preview are also properties which can be configured through XAML.
On the other hand, the Command class defines standard implementations for the CommandBinding’s Executed and CanExecute delegates:
End Sub
Protected Overridable ReadOnly Property SupportsCanExecute() As Boolean
Get
Return False
End Get
End Property
Public Overridable Function CanExecute(ByVal sender As Object, ByVal parameter As Object) As Boolean
Return CanExecute(parameter)
End Function
Public Overridable Function CanExecute(ByVal parameter) As Boolean
Return True
End Function
Private Sub OnExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
Exception = Nothing
Status = WorkingMessage
Dim a As Func(Of Object, Object, Exception) = AddressOf Me.DoExecute
Dim op As System.Windows.Threading.DispatcherOperation = Me.Dispatcher.BeginInvoke(a, Windows.Threading.DispatcherPriority.Background, sender, e.Parameter)
op.Wait()
Dim success As Boolean = (op.Result Is Nothing)
If success Then
Status = ReadyMessage
Else
Dim ex As Exception = op.Result
Status = ex.Message
Exception = ex
End If
End Sub
Private Function DoExecute(ByVal sender As Object, ByVal parameter As Object) As Exception
Dim result As Exception = Nothing
Try
Execute(sender, parameter)
Catch ex As Exception
result = ex
End Try
Return result
End Function
Private Sub OnCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
Try
e.CanExecute = True
If SupportsCanExecute Then
e.CanExecute = CanExecute(sender, e.Parameter)
End If
If e.CanExecute And UseValidation Then
e.CanExecute = Not System.Windows.Controls.Validation.GetHasError(sender)
End If
e.Handled = Handled
Catch
e.Handled = False
End Try
End Sub
This code contains some details which I am going to cover in later posts. The important thing for now is: there are standard event handlers which can be attached to a CommandBinding, and their real work is done in the overridable Execute and CanExecute methods. These methods are supposed to be implemented by derived classes.
Binding to Methods
So far, we have an object which can create a CommandBinding by attaching itself to a Control. The next step is to bind a method of our ViewModel to this object.
For this purpose, I created a subclass of Command, ActionCommand, which defines a property of type Action, i.e. a Delegate for a method that doesn’t take parameters and doesn’t return a result.
Public Property ExecutedAction As Action
Get
Return GetValue(ExecutedActionProperty)
End Get
Set(ByVal value As Action)
SetValue(ExecutedActionProperty, value)
End Set
End Property
Public Overrides Sub Execute(ByVal sender As Object, ByVal parameter As Object)
If ExecutedAction IsNot Nothing Then
ExecutedAction.Invoke()
End If
End Sub
The class overrides the Execute method with an implementation that invokes the method the ExecutedAction delegate is bound to.
In order to be able to bind a value to the ExecutedAction delegate, I created the property as a DependencyProperty. Also, I derived the base class for my Command from Freezable. Freezables are special objects that are optimized for use as resources. What makes them extremely useful in our case is that they inherit the DataContext of the FrameworkElement in whose Resources section they are defined. The DataContext will be our ViewModel object. So, the only thing lacking is a way to create a Delegate for one of its methods through a Binding.
The solution here is a converter. The special converter we use in this case will accept the name of the method as ConverterParameter. Then it will inspect the type of the bound property and the type of the bound object in order to create a Delegate with a matching signature. This is its code:
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
If value Is Nothing OrElse parameter Is Nothing OrElse Not TypeOf parameter Is String OrElse Not GetType([Delegate]).IsAssignableFrom(targetType) Then
Return Nothing
Else
Return WPFGlue.Framework.StickyComponentManager.CreateActionDelegate(value, parameter, targetType)
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Throw New NotSupportedException()
End Function
End Class
And the method which creates the Delegate:
Dim result As [Delegate] = Nothing
If target IsNot Nothing AndAlso Not String.IsNullOrEmpty(methodName) AndAlso GetType([Delegate]).IsAssignableFrom(actionType) Then
Dim targetType As System.Type = target.GetType
Dim methodInfo As System.Reflection.MethodInfo
methodInfo = targetType.GetMethod(methodName, actionType.GetGenericArguments)
If methodInfo IsNot Nothing Then
result = [Delegate].CreateDelegate(actionType, target, methodInfo, False)
End If
End If
Return result
End Function
Gluing it Together in XAML
While this looks quite complex, the good news is that one needs to create this kind of class only once, and from then on can use it to bind to method in XAML only. This is how one would define a Command which binds to a method in the ViewModel:
In this case, the we use the predefined Open RoutedCommand, and bind it to a method called Open (as specified by the ConverterParameter in the Binding) on the current DataContext. Instead of the predefined Open command, we could use any RoutedCommand, even those defined in our own application.
We connect the command to a Control using a Sticky Property which the commanding framework defines:
<Button Command="Open">Open</Button>
As you can see, the Button which triggers the command is completely unaware of its implementation: it just knows that it should work with the Open predefined command, and the rest works through the WPF RoutedCommand infrastructure. This means that you can also take advantage of command routing, like having a MenuItem triggering the same command on different controls depending on which control has keyboard focus. This is more than one gets using the traditional MVVM RelayCommand approach.
Conclusion
Even though it is a bit of work, it is possible to create reusable components which allow it to bind a RoutedCommand to a method of a ViewModel using XAML only. With this technique, it is possible to take advantage of WPF command routing, while at the same time the amount of glue code in the ViewModel is reduced.
You can find the full code and an example project, CommandingExample, in the WPFGluePublished download through the Downloads page.
In posts to come I will cover more details of the WPFGlue Commanding framework, like executing commands on background threads, displaying status and exception messages, or defining CommandSets in order to attach several commands in one line of XAML.
