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 CommandBinding
s 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
- Create a Command in the ViewModel and register the handlers implemented in the ViewModel.
- Create a RibbonCommand which represents the command on the UI.
- 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>
-->
<cmd:CommandSinkBinding Command="{x:Static vm:ViewModel.FirstSampleCommand}"/>
-->
<cmd:MapperCommandBinding Command="{StaticResource FirstRCmd}"
MappedToCommand="{x:Static vm:ViewModel.FirstSampleCommand}">
</Window.CommandBindings>
<DockPanel>
-->
<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;
public ICommand MappedToCommand
{
get { return _mappedToCommand; }
set
{
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.