Click here to Skip to main content
15,881,803 members
Articles / Desktop Programming / WPF
Article

MapperCommandBinding - Mapping commands in WPF

Rate me:
Please Sign up or sign in to vote.
3.88/5 (6 votes)
14 Dec 2008Ms-PL4 min read 50.1K   380   27   8
How to use Microsoft's Ribbon controls and MVVM together.

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:

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

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

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

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

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

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


Written By
Software Developer
Hungary Hungary
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.

Comments and Discussions

 
GeneralAnother way Pin
allancm23-Jun-10 9:35
allancm23-Jun-10 9:35 
GeneralProblem when trying to set the RibbonCommand to RibbonButton by code Behind Pin
Jeanp866-Nov-09 21:02
Jeanp866-Nov-09 21:02 
GeneralAnother approach, exspecialy for the WPF MVVM Toolkit Pin
_chris_g_22-Jul-09 0:41
_chris_g_22-Jul-09 0:41 
GeneralRibbonCommands Pin
Philipp Sumi15-Apr-09 21:12
Philipp Sumi15-Apr-09 21:12 
GeneralCould Be Made Better Pin
#realJSOP15-Dec-08 5:29
mve#realJSOP15-Dec-08 5:29 
GeneralRe: Could Be Made Better Pin
Palesz15-Dec-08 5:33
Palesz15-Dec-08 5:33 
GeneralGreat Solution Thanks! Pin
0xfded15-Dec-08 5:05
0xfded15-Dec-08 5:05 
GeneralRe: Great Solution Thanks! Pin
Palesz15-Dec-08 5:11
Palesz15-Dec-08 5:11 

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.