Introduction
In the last few weeks I’ve started to work on a very interesting UI infrastructure project, based on WPF and Prism. I can’t give many details since it’s kind of confidential, but I want to share a great technique regarding WPF commands. Commanding is a very interesting topic in WPF, especially when dealing with WPF composite applications (known as Prism).
If you’ve had the opportunity to work with WPF, you probably wonder why only few controls: ButtonBase, Hyperlink and MenuItem implement the ICommandSource
interface.
So the question is "What could be done in order to extend other controls such as Selector to execute both WPF and Prism (custom) commands?"
To answer this question, let's define the problem and provide functional requirements.
Problem
Working with either composite or monolithic UI applications, it is reasonable to treat application domain actions triggered by the user interface as commands, so that they can be handled by the Presentation Model or the Controller and be bound to an availability state. For example, having a ListBox
, we want it to execute a command to fetch the rest of the item details from a service on item selection. The usual way to achieve this in WPF is to register the Selector.SelectionChanged
routed event via XAML, handle it in the C# behind XAML file and delegate the call to the Presentation Model or Controller. This approach is not only convoluted but also inappropriate by design. But I’ll leave the design for another discussion.
Extracting at least one requirement from the example above, we want the Selector control to be able to execute a command when the Selector.SelectionChanged
routed event is being fired.
There are many ways to solve this issue, each has its pros and cons.
Solutions
Custom Control
A straight forward approach is to create a new custom control, for example: CommandListBox
, implement the ICommandSource
and execute the command on item selection.
Although this solution is fairly reasonable, it requires creation of a custom control for each type of control that doesn’t natively support command.
Attached Properties
So let’s look at another approach. If you’re familiar with the outstanding WPF Attached Property mechanism you could solve this as follows:
<ListBox
local:CommandProvider.Command="{x:Static local:CommonCommands.Do}"
local:CommandExtender.Handler="{x:Static
local:CommandHandlers.SelectorSelect}"
local:CommandExtender.Parameter="{Binding Path=/}" />
In this case, the CommandProvider
is a static
class, it provides commanding services via attached properties, where:
- Command is the command instance to execute
- Handler is an instance of a custom type which provides the execute behavior, such as “execute the command on
Selector.SelectionChanged
event”
- Parameter is the command parameter
As you can see, this approach is much more flexible and extensible since it can be used on any kind of UIElement
, and without the unnecessary creation of a custom control.
Multiple Commands
Since both the custom implementation of ICommandSource
and the attached properties approaches support only one command at a time, being executed by only one behavior, I’ve decided to extend the attached properties approach to support more than one command to be executed by more than one behavior.
<ListView x:Name="list" ItemsSource="{Binding Emails}"
IsSynchronizedWithCurrentItem="True">
<ts:CommandSource.Trigger>
<ts:CommandTriggerGroup>
<ts:EventCommandTrigger
RoutedEvent="UIElement.PreviewMouseLeftButtonUp"
Command="{Binding Path=DownloadEmail}"
CustomParameter="{Binding ElementName=list,
Path=SelectedValue}" />
<ts:EventCommandTrigger
RoutedEvent="UIElement.PreviewMouseRightButtonUp"
Command="{Binding Path=MarkAsRead}"
CustomParameter="{Binding ElementName=list,
Path=SelectedValue}" />
<ts:EventCommandTrigger
RoutedEvent="UIElement.PreviewMouseLeftButtonDown"
Command="{Binding Path=OpenEmail}"
CustomParameter="{Binding ElementName=list,
Path=SelectedValue}" />
</ts:CommandTriggerGroup>
</ts:CommandSource.Trigger>
</ListView>
...
<Expander IsExpanded="{Binding Path=DummyProperty}" Header="Contact">
<ts:CommandSource.Trigger>
<ts:PropertyCommandTrigger
Property="Expander.IsExpanded"
Value="True"
CustomParameter="{Binding}"
Command="{Binding Path=DownloadContact,
RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=Window}}" />
</ts:CommandSource.Trigger>
...
</Expander>
I’ve replaced the CommandProvider
with CommandSource
in the markup code above and the three attached properties with only one: Trigger
. The real difference here is that the Trigger
attached property is of type ICommandTrigger
.
- The
ICommandTrigger
interface is implemented by three classes: EventCommandTrigger
, PropertyCommandTrigger
and CommandTriggerGroup
.
EventCommandTrigger
– executes a command when a routed event is being fired.
PropertyCommandTrigger
– executes a command when a dependency property is being changed, and a specific value is met.
CommandTriggerGroup
– represents a collection of commands. Using this class as shown above, you can attach more than one command trigger.
Note that both the EventCommandTrigger
and PropertyCommandTrigger
derive from the WPF Freezable type. This provides an option to be bound to elements in the visual tree. As for the CommandTriggerGroup
I’ve used the FreezableCollection
as its base class.
Command and Command Parameter
Since CommandTrigger
translates routed events and dependency property values into Command, there should be an easy way to have both the routed event and property value, and another user parameter as one parameter of the command. To handle this situation, I’ve created the CommandParameter
types.
OpenEmail = new RoutedCommand();
CommandBinding cmdBinding3 = new CommandBinding(OpenEmail);
cmdBinding3.Executed += (s, e) =>
{
var parameter = EventCommandParameter<EmailMessage,
MouseButtonEventArgs>.Cast(e.Parameter);
if (parameter.EventArgs.ClickCount == 2)
{
parameter.CustomParameter.MarkAsRead();
MessageBox.Show(parameter.CustomParameter.Content,
parameter.CustomParameter.Subject);
}
};
cmdBinding3.CanExecute += (s, e) =>
{
e.CanExecute = true;
};
CommandBindings.Add(cmdBinding3);
Both the EventCommandParameter
and PropertyCommandParameter
types derive from the CommandParameter
type. You can think of these types as simple wrappers around the Routed Event or Dependency Property and Custom Parameter. From the sample above, you can see that each type of the CommandParameter
type has a special Cast<T1, T2>
helper method. This simplifies the explicit casting operations of both the custom parameter and routed event argument or dependency property value.
Conclusion
Now that we can use commands anywhere, we can use only Data Templates as views for our Presentation Model. This mechanism comes in handy especially in Composite Applications where presenters are usually laid out in regions and Data Templates generate the view.
You may download the full code from here.
History
- 23rd April, 2009: Initial post