Click here to Skip to main content
Click here to Skip to main content

MapperCommandBinding - Mapping commands in WPF

By , 14 Dec 2008
 

Introduction

In this article, I will show how can you create a CommandBinding which "maps" one command to another in WPF, so you can use your MVVM architecture with the Microsoft Ribbon controls, avoiding the spaghetti code in "code-behind" files.

Some reading about the Ribbon controls and the MVVM:

Background

RibbonControls and Commands

Not so long ago, Microsoft released the Ribbon controls for WPF. I started using it in one of my projects, but I realized that commands are not used like we would use them in WPF. Using the Ribbon controls, the RibbonCommand represents a command on the ribbon (on the UI), it specifies how we will show it:

...
<Window.Resources>
  <r:RibbonCommand
      x:Key="FirstRCmd"
      LabelTitle="1st Cmd"
      SmallImageSource="{StaticResource BlueEllipse}"
      LargeImageSource="{StaticResource BlueEllipse}"
      CanExecute="CanExecute_First"
      Executed="Execute_First"
      ToolTipTitle="3rd Command"
      ToolTipDescription="This is the 3rd command"/>
</Window.Resources>
...

In the controls (RibbonButton, ...) of the ribbon, we cannot specify how the control should look like, but we can give a Command (in this case, a RibbonCommand) to the control, so the representation of the command (title, images, tooltip, ...) should be set in the RibbonCommand object.

MVVM

For my WPF projects, I use the MVVM to avoid spaghetti code. In most cases, I don't need to write any "logic" into the code-behind file of a window. So, the view is specified in XAML and it is connected with the ViewModel via bindings.

Using the MVVM, I can keep my code as simple as possible, and it is very reusable too.

Usually, I don't use the simple CommandBindings because then I should create the CanExecute and Executed methods in the code-behind file. Instead, I use Josh Smith's CommandSinkBinding technique.

Problems

If you want to use the MVVM and the Ribbon controls together, you have two options:

1. Methods in the code-behind

Create the CanExecute and Executed methods for the RibbonCommand in the code-behind, and in those methods, delegate the call to the methods of the ViewModel. There will be a lots of code in the code-behind, which is hard to manage. Lots of CanExecute and Executed methods, which only delegates the code.

2. Register the RibbonCommands in the ViewModel

You can create the RibbonCommands in a ResourceDictionary or in a static class (in this case, you should specify the properties of the command in code - yak). Then, you should register these RibbonCommands in the ViewModel like this:

RegisterCommand( MyRibbonCommands.SampleRibbonCommand,
    param => CanExecuteSampleMethod( param ),
    param => SampleMethod( param ) );

But in this case, the ViewModel and the View would have been tied together, the ViewModel would know about the actual view, the view of the RibbonCommands.

Solution

The concept

  1. Create a Command in the ViewModel and register the handlers implemented in the ViewModel.
  2. Create a RibbonCommand which represents the command on the UI.
  3. Tell the RibbonCommand that the real command which would be executed is the one registered in the ViewModel.

So, we should "map" the RibbonCommand to the ViewModel's command, somehow.

The concrete solution - MapperCommandBinding

I solved this problem based on the described solution in a very general way. I created a CommandBinding called MapperCommandBinding which simply maps a command to any other command. The usage of the MapperCommandBinding is shown below (the important parts in bold):

<Window
  ...
  cmd:CommandSinkBinding.CommanSink="{Binding}"
  ...>

  <Window.Resources>
    <r:RibbonCommand
        x:Key="FirstRCmd"
        LabelTitle="1st Cmd"
        SmallImageSource="{StaticResource BlueEllipse}"
        LargeImageSource="{StaticResource BlueEllipse}"/>
  </Window.Resources>

  <Window.CommandBindings>
      <!-- Commands used in this window which comes from the ViewModel -->
    <cmd:CommandSinkBinding Command="{x:Static vm:ViewModel.FirstSampleCommand}"/>
  
    <!-- Command mapping -->
    <cmd:MapperCommandBinding Command="{StaticResource FirstRCmd}"
       MappedToCommand="{x:Static vm:ViewModel.FirstSampleCommand}">
  </Window.CommandBindings>

  <DockPanel>
  
      <!--Sample Ribbon for the MapperCommandBinding-->
      <r:Ribbon DockPanel.Dock="Top">
          <r:RibbonTab Label="Sample">
              <r:RibbonGroup>
                  <r:RibbonButton Command="{StaticResource FirstRCmd}" />
              </r:RibbonGroup>
          </r:RibbonTab>
      </r:Ribbon>      
      ...
  </DockPanel>
  ...
</Window>

How it works?

The MapperCommandBinding is the descendant of the CommandBinding class. It has an extra property called MappedToCommand. If this property is set, the MapperCommandBinding subscribes to the CanExecute and Executed events.

public class MapperCommandBinding : CommandBinding
{

    private ICommand _mappedToCommand = null;

    /// <summary>
    /// The command which will executed instead of the 'Command'.
    /// </summary>
    public ICommand MappedToCommand
    {
        get { return _mappedToCommand; }
        set
        {
            //mapped command cannot be null
            if ( value == null )
                throw new ArgumentException( "value" );

            this._mappedToCommand = value;

            this.CanExecute += OnCanExecute;
            this.Executed += OnExecuted;
        }
    }
    
    ...
}

The OnExecuted event handler looks like this:

public class MapperCommandBinding : CommandBinding
{
  ...
    protected void OnExecuted( object sender, ExecutedRoutedEventArgs e )
    {
        if ( MappedToCommand is RoutedCommand && e.Source is IInputElement )
            ( MappedToCommand as RoutedCommand ).Execute( e.Parameter, 
                                          e.Source as IInputElement );
        else
            MappedToCommand.Execute( e.Parameter );
        e.Handled = true;
    }
    ...
}

If the command is a RoutedCommand and the source is an IInputElement, it restarts the search for a CommandBinding in the logical tree from the original source, but at this time, it will be looking for a CommandBinding which is bound to the MappedToCommand.

So, in the example above, if we push the first RibbonButton, there goes a search for a CommandBinding which is bound to the FirstRCmd. It will find that inside the <Window.CommandBindings>. It is a MapperCommandBinding, so it will restart the search from the first RibbonButton, but at this time, it will be looking for a CommandBinding to the FirstSampleCommand. It will find it inside the <Window.CommandBindings>. It is handled by the ViewModel, so we can bind the RibbonCommand to our ViewModel's command. To do this, we only need one line of code, which look like this:

<cmd:MapperCommandBinding Command="{StaticResource FirstRCmd}" 
   MappedToCommand="{x:Static vm:ViewModel.FirstSampleCommand}">

Conclusion

I think the problem has been solved in a very simple and declarative way. We just say which command should really handle our RibbonCommand. We couldn't have done it easier. (If you know a better way, let me know, please.)

The source code and the sample can be found at WPFExtensions.

History

  • 13 Dec. 2008 - Initial revision.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Palesz
Software Developer
Hungary Hungary
Member
András Pálinkás aka. "Pálesz" is a student at the Budapest University of Technology and Economics.
 
He is interested in software visualizations, graph drawings. His favoutires are the .NET technologies, especially the Windows Presentation Foundation.
 
He works for the Ericsson as a Tool Designer (C++, Java technologies).
 
In his spare time he likes to play soccer, swim or hang out with his friends/family.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralAnother waymemberallancm23 Jun '10 - 9:35 
I found another way to do it browsing in the web.
 
This is the link MVVM - How to integrate the Office Ribbon respecting the pattern (especially the commands).
 
It is similar but the code the be used is simpler, I think.
This is the xaml code when using the solution:
 
<Window.Resources><br>
  <fwk:CommandReference x:Key="MyCommandReference"<br>
  Command="{Binding MyViewModelCommand}" /><br>
<br>
  <fwk:RibbonCommandExtended x:Key="cmd_MyCommand" LabelTitle="A labek"<br>
  LabelDescription="A description"<br>
  Command="{StaticResource MyCommandReference}"<br>
  LargeImageSource="/MVVMRibbon;component/antoine64x64.jpg"<br>
  SmallImageSource="/MVVMRibbon;component/antoine64x64.jpg" /><br>
</Window.Resources><br>
 
Thanks for your solution also.
 
Let's see when the RibbonControlsLibrary will support the ICommand directly.
GeneralProblem when trying to set the RibbonCommand to RibbonButton by code BehindmemberJeanp866 Nov '09 - 21:02 
I'm not really sure if this is the correct place to ask this question , but maybe you can help me out.
I'm learning WPF by my self, and i want to update the old sytem using WPF and the new Ribbon Control.
I'm creating all ribbon stuff(tab, group, controls) dynamically by code behind. I want to fill all tabs and groups from database. But when i wanted to set the LabelTitle, ToolTipTitle, and ToolTipDescription for the RibbonButton, i noticed that they must go in a RibbonCommand and then set that Command to the Button. right?. When i do it by xaml there is no problem. But when i do it on code behind , the labelTitle appears, but the RibbonButton is disabled. I read something about this problem, and it says that RibbonCommand needs to have Execute and CanExecute. My question is, how can i deal with that by code behind. Here is my code:

'TAB
Dim rbnTab As New RibbonTab
rbnTab.Label = "Orders"
 
'GROUP
Dim rbnGroup As New RibbonGroup
 
'COMMAND FOR THE GROUP
Dim rbnGroupCommand As New RibbonCommand
rbnGroupCommand.LabelTitle = "Grower Order"
 
'BUTTON
Dim rbnButton1, rbnButton2 As New RibbonButton
rbnButton1.Content = "Grower Order"
rbnButton2.Content = "Purchase Order"
 

Dim rbnButtonCommand1 As New RibbonCommand
 
rbnButtonCommand1.LabelTitle = "Grower"
rbnButtonCommand1.ToolTipTitle = "Grower Order"
rbnButtonCommand1.ToolTipDescription = "This option allows you to create new Grower Orders"
 

rbnButton1.AddHandler(RibbonButton.ClickEvent, New RoutedEventHandler(AddressOf RibbonButton_Click))
rbnButton2.AddHandler(RibbonButton.ClickEvent, New RoutedEventHandler(AddressOf RibbonButton_Click))
 
'here is my problem. When i set the command , the ribbonButton appears disabled
rbnButton1.Command = rbnButtonCommand1
 
rbnGroup.Command = rbnGroupCommand
rbnGroup.Controls.Add(rbnButton1)
rbnGroup.Controls.Add(rbnButton2)
 
rbnTab.Groups.Add(rbnGroup)
 
ribbon.Tabs.Add(rbnTab)
 
'On the XMAL there is just this:
<Grid>
<r:Ribbon Title="TESTING" x:Name="ribbon" >

<r:Ribbon>
 

</Grid>
 
I will really apreciate your help.
 
Thanks in advance.
GeneralAnother approach, exspecialy for the WPF MVVM Toolkitmember_chris_g_22 Jul '09 - 0:41 
Thank you for your article. I've found another solution, which fits good with the new WPF MVVM template for Visual Studio (http://wpf.codeplex.com/Wiki/View.aspx?title=WPF%20Model-View-ViewModel%20Toolkit[^]). Check out this site: http://wpf.codeplex.com/Thread/View.aspx?ThreadId=52089[^]
Cheers,
Chris
GeneralRibbonCommandsmemberPhilipp Sumi15 Apr '09 - 21:12 
Looking at CodePlex recently, it appears that MS will completely dump RibbonCommand, which is a good thing. For the time being, I think this as a valid workaround, especially as RibbonCommand appears to introduce a severe memory leak if code-behind event listeners are being used (blog post).
 
Cheers for the sample Smile | :)
Philipp
 
code hard @ hardcodet.net

GeneralCould Be Made BettermvpJohn Simmons / outlaw programmer15 Dec '08 - 5:29 
You should show the way it would have to be done without your mapper, so you can show the benefits of using your mapper.
 

"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"...the staggering layers of obscenity in your statement make it a work of art on so many levels." - Jason Jystad, 10/26/2001

GeneralRe: Could Be Made BettermemberPalesz15 Dec '08 - 5:33 
Thank you the comment.
I have explained 2 "bad" solution, but i will write some examples for those then.
Thank you for the constructive comment, again.
GeneralGreat Solution Thanks!member0xfded15 Dec '08 - 5:05 
I just started working with the Ribbon control and found myself in the exact situation (also using Josh's great CommandSink code). I hadn't gotten around to solving the problem, so you beat me to it and did a great job.
 
Thanks!
GeneralRe: Great Solution Thanks!memberPalesz15 Dec '08 - 5:11 
Thanks, you're welcome! Smile | :)
I'm glad that I can help.

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 14 Dec 2008
Article Copyright 2008 by Palesz
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid