Click here to Skip to main content
15,879,535 members
Articles / Desktop Programming / WPF

WPF Commands Everywhere

Rate me:
Please Sign up or sign in to vote.
4.85/5 (18 votes)
23 Apr 2009CPOL4 min read 55.2K   48   11
This article covers techniques for using WPF commands with controls that don't support commands natively

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:

XML
<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.

XML
<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>

...

XML
<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>
Image1.pngImage2.pngImage3.png

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.

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect CodeValue
Israel Israel
Tomer Shamam is a Software Architect and a UI Expert at CodeValue, the home of software experts, based in Israel (http://codevalue.net). Tomer is a speaker in Microsoft conferences and user groups, and in-house courses. Tomer has years of experience in software development, he holds a B.A degree in computer science, and his writings appear regularly in the Israeli MSDN Pulse, and other popular developer web sites such as CodePlex and The Code Project. About his thoughts and ideas you can read in his blog (http://blogs.microsoft.co.il/blogs/tomershamam).

Comments and Discussions

 
QuestionIs there a download? Pin
New Dev9-Jul-13 6:30
New Dev9-Jul-13 6:30 
GeneralMy vote of 5 Pin
bahman aminipour25-Mar-13 8:00
bahman aminipour25-Mar-13 8:00 
QuestionSource code Pin
billy_bones2-Dec-12 12:07
billy_bones2-Dec-12 12:07 
QuestionSource Code not reachable ? Pin
hulinning29-Oct-12 2:41
hulinning29-Oct-12 2:41 
GeneralMy vote of 4 Pin
llox12-Dec-11 23:58
llox12-Dec-11 23:58 
GeneralMy vote of 5 Pin
John Adams16-Mar-11 3:17
John Adams16-Mar-11 3:17 
great article and very nice solution!
QuestionTomer, do you use VB.NET as well? Pin
Shimmy Weitzhandler7-Jan-10 11:51
Shimmy Weitzhandler7-Jan-10 11:51 
QuestionQuestion about implementation detail [modified] Pin
__alex29-Apr-09 22:53
__alex29-Apr-09 22:53 
AnswerRe: Question about implementation detail Pin
Tomer Shamam1-May-09 22:41
Tomer Shamam1-May-09 22:41 
GeneralGood work, Tomer Pin
Ron Gramann24-Apr-09 0:54
Ron Gramann24-Apr-09 0:54 
GeneralRe: Good work, Tomer Pin
Tomer Shamam24-Apr-09 10:30
Tomer Shamam24-Apr-09 10:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.