Synopsis
The ribbon controls - introduced by Office 2007 - are available for free on the Microsoft Office web site (French users should set the language to "English" to access the ressources). They can leverage the user's experience of your application and are pretty simple to use.
When I wanted to add them into one of my applications, I realized that it was breaking the M-V-VM pattern.
In this post, we will see how to use the Ribbon
, then what exactly is the issue and finally examine the solution I use as a work-around.
How to Use the Ribbon
This is very easy. Here are the steps:
A central part of the Ribbon
library is the RibbonCommand
. A RibbonCommand
is a WPF command plus a lot of things related to how its presented: a label, a description, a large image, a small image, etc. Then every button
, combobox
, checkbox
, ... used in the Ribbon
use this information to change the way in which they are presented. Here is a little example:
<Window x:Class="MVVMRibbon.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;
assembly=RibbonControlsLibrary"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<r:RibbonCommand x:Key="MyFirstCommand" LabelTitle="A command"
LabelDescription="A command description"
LargeImageSource="/MVVMRibbon;component/antoine64x64.jpg"
SmallImageSource="/MVVMRibbon;component/antoine64x64.jpg"
Executed="RibbonCommand_Executed" CanExecute="RibbonCommand_CanExecute" />
<r:RibbonCommand x:Key="ApplicationMenuCommand"
LargeImageSource="/MVVMRibbon;component/antoine64x64.jpg"
SmallImageSource="/MVVMRibbon;component/antoine64x64.jpg" />
</Window.Resources>
<DockPanel LastChildFill="True">
<r:Ribbon DockPanel.Dock="Top">
<r:Ribbon.QuickAccessToolBar>
<r:RibbonQuickAccessToolBar Visibility="Collapsed" />
</r:Ribbon.QuickAccessToolBar>
<r:Ribbon.ApplicationMenu>
<r:RibbonApplicationMenu Command="{StaticResource ApplicationMenuCommand}" />
</r:Ribbon.ApplicationMenu>
<r:RibbonTab Label="A first tab">
<r:RibbonGroup>
<r:RibbonButton Command="{StaticResource MyFirstCommand}" />
</r:RibbonGroup>
</r:RibbonTab>
<r:RibbonTab Label="A second tab"></r:RibbonTab>
</r:Ribbon>
<FlowDocumentReader />
</DockPanel>
</Window>
Why Using the RibbonCommand is Breaking the Pattern
As you can see in the code above, when you declare the RibbonCommand
s in the XAML, you have to set Execute
and CanExecute
event's handler. These handlers are set in the code behind and this is what breaks the pattern.
So why not only declare RibbonCommand
inside the viewModels? Because this will put presentation information (those inside the RibbonCommand
like images, description) inside the ViewModel which must be decoupled from the way the data is presented.
Actually, only declaring RibbonCommands
inside the ViewModel breaks the pattern because it exist a very strong link between the data and how its presented in these objects.
An another thing is that you can't bind anything to the Ribbon commands because: A 'Binding
' cannot be set on the 'XXX' property of type 'RibbonCommand
'. A 'Binding
' can only be set on a DependencyProperty
of a DependencyObject
.... Yes, a RibbonCommand
is not a DependencyObject
.
So I Can't Use the Ribbon?
Nooooo! A solutions exists: first, you can create some kind of proxies to the command which will make the commands available as a resource in the views(CommandReference
) through binding. Then the view will be responsible for creating the RibbonCommand
s from these commands in the resources. To this purpose, we'll have to extend the standard RibbonCommand
to make it accepts a Command
as a property.
Ok, ok, I heard your question: why not directly make the extended RibbonCommand
s acts as a proxy? The answere is that RibbonCommand
does not inherit from DependencyObject
and so we can't bind anything on it ! (Which means, by the way, that we can't bind the commands of the viewModels directly to them).
I did not invent this technique, it's from:
The Proxy for the Commands
As pointed out in this article, I call them CommandReference
.
We declare a DependencyProperty
on which we will bind the command in the ViewModel. As you can see, this class is also an ICommand
: all the calls will be translated to the binded command.
public class CommandReference : Freezable, ICommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command",typeof(CommandReference),
new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
#region ICommand Members
public bool CanExecute(object parameter){
return (Command != null)?Command.CanExecute(parameter):false;
}
public void Execute(object parameter){ Command.Execute(parameter);}
public event EventHandler CanExecuteChanged;
private static void OnCommandChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandRef = d as CommandReference;
if (commandReference != null)
{
ICommand lastCommand = e.OldValue as ICommand;
if (lastCommand != null) lastCommand .CanExecuteChanged -=
commandRef .CanExecuteChanged;
ICommand nextCommand = e.NewValue as ICommand;
if (nextCommand != null) nextCommand .CanExecuteChanged +=
commandRef .CanExecuteChanged;
}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore()
{
return new CommandReference();
}
#endregion
The Extended RibbonCommands
We simply add an ICommand
property to the RibbonCommand
which we will be able to fill in with a StaticResource
.
public class RibbonCommandExtended : RibbonCommand
{
private ICommand _command;
public ICommand Command
{
get { return _command; }
set
{
_command = value;
if (_command != null)
{
this.CanExecute += us_CanExecute;
this.Executed += us_Executed;
}
else
{
this.CanExecute -= us_CanExecute;
this.Executed -= us_Executed;
}
}
}
private void us_Executed(object sender, ExecutedRoutedEventArgs e)
{
Command.Execute(e.Parameter);
}
private void us_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = Command.CanExecute(e.Parameter);
}
}
Then, What Will My XAML Look Like?
Here it is, especially for you, very simple:
<Window.Resources>
<fwk:CommandReference x:Key="MyCommandReference"
Command="{Binding MyViewModelCommand}" />
<fwk:RibbonCommandExtended x:Key="cmd_MyCommand" LabelTitle="A labek"
LabelDescription="A description"
Command="{StaticResource MyCommandReference}"
LargeImageSource="/MVVMRibbon;component/antoine64x64.jpg"
SmallImageSource="/MVVMRibbon;component/antoine64x64.jpg" />
</Window.Resources>
Then you use the RibbonCommandExtended
as you will have used the standard RibbonCommand
.
Isn't it a little long to make something pretty simple? The answer is definitively yes, but Microsoft seems to be working on a new version of the Ribbon
which will respects the M-V-VM pattern...
This is not possible because as I pointed out before, the RibbonCommand
s are not DependencyObject
and so we can't attach properties to them !
Links
Here are some links you may find interesting on the same subject:
CodeProject