Essential WPF

WPF Commands Everywhere

It has been a long time since I’ve written something in my blog and there are many items in my stack. Unfortunately, I don’t have the time to pop all, so I’ll try to peek few of my best thoughts.

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 can say that I work with very talented people, and Lior Rozner from Microsoft Israel is one of them.

So enough about what I’m doing, let’s talk about a very interesting topic in WPF & Prism: Commands. 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 lets define the problem and provide functional requirements.

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.

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.

So let’s look at another approach recommended by Lior Rozner.

Attached Properties

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>

image image image

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.

 

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.

Comments

Edwin Foh said:

Very helpful and insightful article. This is exactly what people out there need to leverage controls that do not implement ICommandSource, and overcomes the limitations behind using that approach.

Good stuff! thanks for sharing.

Ed

# April 20, 2009 7:45 AM

WPF Commands: Better approach to using ICommandSource « Code Blitz said:

Pingback from  WPF Commands: Better approach to using ICommandSource &laquo; Code Blitz

# April 20, 2009 9:58 AM

Avi Shilon said:

Hey, great solution Tomer!

P.S.

Tell Lior I say hi :)

# April 21, 2009 2:49 PM

Julian Dominguez said:

Very nice. I like the approach of having freezables as attached properties... didn't realize that was the way for binding to the DataContext to work!

I'm also intrigued on the Prism work you are doing :)

Looking forward to reading some more.

# April 22, 2009 3:16 AM

Tomer Shamam said:

Thanks Avi! I will :-)

# April 23, 2009 2:22 PM

Ger said:

Hi,

Thanks for a very helpful article. I am using this EventCommandTrigger for PreviewMouseLeftButtonUp event. The problem I have is that the event gets also fired when the header of the List view is clicked or the scrollbar is moved or the column width is changed which is not desired. Could you please suggest me something to avoid it. Thanks

-Ger

# May 11, 2009 11:00 AM

Tomer Shamam said:

Hi Ger,

What exactly you do with the preview event?

Btw, you can always check in the event args that the e.Source is ListBox or something like that.

# May 11, 2009 11:14 AM

Ger said:

Sorry for a stupid question. Selector.SelectionChanged just solves my problem.

-Thanks

# May 11, 2009 11:29 AM

Juraj said:

Hi. Nice, thanks. But it is not working for me. I want to respond to 'Sorting' event in Datagrid (http://www.codeplex.com/wpf). My xaml looks like this:

<ts:CommandSource.Trigger>

               <ts:CommandTriggerGroup>

                   <ts:EventCommandTrigger RoutedEvent="dg:DataGrid.Sorting"

Command="{Binding Path=Sorting}" />

               </ts:CommandTriggerGroup>

</ts:CommandSource.Trigger>

I get error: 'Attached event field missing'.

With <ts:EventCommandTrigger RoutedEvent="dg:DataGrid.PreviewMouseLeftButtonUp"

Command="{Binding Path=Sorting}" /> it works. Any idea?

# June 24, 2009 1:57 PM

Twitted by itshooter said:

Pingback from  Twitted by itshooter

# June 27, 2009 4:30 PM

Tomer Shamam said:

Hi Juraj,

Does Sorting is routed event?

# June 30, 2009 9:07 PM

Tomasz Kubacki said:

I've found that there are problems with EventCommandTrigger when used inside template (for example instance treeviewItem template) any is there ay place where i can send bug request ? ;-)

Command simply is not executed.

# July 23, 2009 4:49 PM

Tomer Shamam said:

Hi Tomasz, you can place bug request here :-)

What exactly the problem is?

# July 25, 2009 9:52 AM

The road to C# master trapemiya said:

【WPF】 MVVMでListBoxのSelectionChangedイベントをViewModelで拾いたい。

# August 4, 2009 10:27 AM

FR said:

I really like this approach and want to make use of this code. But unfortunatly I have a similar problem. I am using DataTemplates for most parts of my XAML code and it seems the EventCommandTrigger cannot establish the Binding. I got the error message: "System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=FilterRangeCommand.Command; DataItem=null; target element is 'EventCommandTrigger' (HashCode=3429838); target property is 'Command' (type 'ICommand')". If I move the code outside of the DataTemplate it is working fine.

I am going to find the error, but was wondering if this problem is already solved

# September 15, 2009 2:50 PM

Tomer Shamam said:

Hi Guys,

Sorry for the inconvenience, it's probably a bug.

I'll look at that later and provide a fix.

Thanks.

# September 15, 2009 3:06 PM

Tomer Shamam said:

FR, could you please provide a code snippet of whar you're trying to do, since in my demo I uses DataTemplate for the EmailMessage and it works just fine.

Tomer

# September 15, 2009 3:15 PM

FR said:

Tomer, you can reproduce the same error message that I get in your example. Just put a TabControl around your DockPanel and put the DockPanel into a second Tab:

<TabControl>

  <TabItem Header="tab1"/>

  <TabItem Header="tab2">

    <DockPanel>...your content </DockPanel>

  </TabItem>

</TabControl

Now, when you start the app and switch to tab2, you get the same error message that I have. I tried to Debug it, but I am not sure what exactly the problem is. Some help would be great.

# September 23, 2009 11:50 AM

BusinessRx Reading List said:

When .NET arrived on the scene it promoted the use of events and delegates to decouple code. Chris Sells

# September 30, 2009 3:23 AM

Death to .NET Events | AnswerMyQuery said:

Pingback from  Death to .NET Events | AnswerMyQuery

# September 30, 2009 6:31 AM

Community Blogs said:

When .NET arrived on the scene it promoted the use of events and delegates to decouple code. Chris Sells

# October 1, 2009 4:02 PM

Essential WPF said:

Up until Blend 3, UI designers had difficult time prototyping a real functional UI. The main problem

# November 7, 2009 12:43 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: