MVVM - How to integrate the Office Ribbon respecting the pattern (especially the commands)
4 March 2010Synopsis
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 application I realized that it was broking 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 :
- Download the library on the web site (http://msdn.microsoft.com/fr-fr/office/aa973809.aspx),
- Add a reference to the dll in your project and dclare in the XAML this XML namespace : clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary
- Then you are free to use the ribbon's controls.
A central part of the Ribbon library is the RibbonCommand. A RibbonCommand is a WPF command plus a lot of things related to how it's presented : a label, a description, an large image, a small image, etc.... Then every button, combobox, checkbox, ... used in the Ribbon use these infos to change the way 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"> <!--I hide the QuickAccessToolBar because I have no use of it--> <r:Ribbon.QuickAccessToolBar> <r:RibbonQuickAccessToolBar Visibility="Collapsed" /> </r:Ribbon.QuickAccessToolBar> <!--Here is the ApplicationMenu : the bubble acting as a main menu in Office--> <r:Ribbon.ApplicationMenu> <r:RibbonApplicationMenu Command="{StaticResource ApplicationMenuCommand}" /> </r:Ribbon.ApplicationMenu> <!-- And finally the well-know "tabs"--> <r:RibbonTab Label="A first tab"> <!--The controls are grouped in the tabs--> <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 broking the pattern
As you can see in the code above, when you declare the RibbonCommands in the XAML, you have to set a Execute and CanExecute event's handler. These handlers are set in the code behind and this is what breaks the pattern.
Sow why not only declare RibbonCommand inside the viewModels ? Because this will put presentation informations (those inside the RibbonCommand like images, description) inside the ViewModel which one 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 it's 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 ressource in the views(CommandReference) through binding. Then the view will be responsible to create the RibbonCommands as they which from these commands in the ressources. To this purpose we'll have to extend the standard RibbonCommand to make them accepts a Command as a property.
Ok, ok, I heard your question : why not directy make the extended RibbonCommands acts as a proxy ? The answere is that RibbonCommand does not inherits 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 a 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(ICommand), 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 (oldCommand != null) lastCommand .CanExecuteChanged -= commandRef .CanExecuteChanged; ICommand nextCommand = e.NewValue as ICommand; if (newCommand != null) nextCommand .CanExecuteChanged += commandRef .CanExecuteChanged; } } #endregion #region Freezable protected override Freezable CreateInstanceCore() { return new CommandReference(); } #endregion
The extended RibbonCommands
We simply add a ICommand property to the RibbonCommand wich one we will be abble to fill in with a StaticRessource.
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 looks 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 the Microsoft seems to be working on new version of the ribbon which will respects the M-V-VM pattern...
Why not using our RamoraPattern ?
This is not possible because as I pointed out before, the RibbonCommands are not DependencyObject and so we can't attach properties to them !
Links
Here are some links you may find interesting on the same subjects :
- MapperCommandBinding - Mapping commands in WPF,
- A walktrough inside the ribbon functionnalities,
- A french article example of how to use the ribbon,
- Another WPF/C# ribbon
- By JonathanANTOINE@falsemail.com
- - WPF
- - Tags :
Comments
Thanks for your solution.
Do you have a solution to dynamically load RibbonButtons of a RibbonGroup?
I'm trying to do this ... but the Controls property of the RibbonGroup doesn't support a setter, just a getter.
So even if you provide a method to hide a non dependency property or command, it's difficult to use it on a read-only property ....
I'm hopping that you have a solution for me and I'm doing a big mistake in my post :D
Guillaume.
Gigi070: Use Controls.Add() to dynamically add new buttons.
@Emperor :The group visual does not update when you do this even if the list contains the items...
@Jonathan ANTOINE : after you are finished with adding/removing/clearing Controls property call OnApplyTemplate() on RibbonGroup to update the visuals.
@Emperor :Thx, it works fine !
I'm developping an application which bases on PrismV2 and the designPattern MVVM.
Thus I develop every module separately and I use a main interface to call them (Shell)
My Shell contains OfficeRibbon
I still have some problems to understand
can you explain me how I have to use RibbonCommand?
Could you please upload the demo project?
Try Fluent Ribbon Control Suite (http://fluent.codeplex.com). Fluent Ribbon is more MVVM-friendly, you can find MVVM sample there.
I am very interested in this sphere and reading this post I have known many new things, which I have not known before. Thanks for publishing this great article here.