Click here to Skip to main content
15,868,292 members
Articles / Desktop Programming / WPF

MVVM - How to integrate the Office Ribbon respecting the pattern (especially the commands)

Rate me:
Please Sign up or sign in to vote.
4.11/5 (6 votes)
5 Oct 2010Ms-PL4 min read 28.1K   21   5
How to integrate the Office Ribbon respecting the pattern (especially the commands)

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:

  • Download the library on the web site
  • Add a reference to the DLL in your project and declare in the XAML this XML namespace:
    C#
    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 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:

MVVMRibbonExample
XML
<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 Breaking the Pattern

As you can see in the code above, when you declare the RibbonCommands 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 RibbonCommands 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 RibbonCommands 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.

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

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

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

Why Not Use 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 subject:

Shout it kick it on DotNetKicks.com

This article was originally posted at http://blog.lexique-du-net.com/index.php

License

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


Written By
Software Developer http://wpf-france.fr
France (Metropolitan) France (Metropolitan)
Jonathan creates software, mostly with C#,WPF and XAML.

He really likes to works on every Natural User Interfaces(NUI : multitouch, touchless, etc...) issues.



He is awarded Microsoft MVP in the "Client Application Development" section since 2011.


You can check out his WPF/C#/NUI/3D blog http://www.jonathanantoine.com.

He is also the creator of the WPF French community web site : http://wpf-france.fr.

Here is some videos of the projects he has already work on :

Comments and Discussions

 
QuestionWhat about CommandParameters? Pin
Jay R. Wren5-Oct-10 10:33
Jay R. Wren5-Oct-10 10:33 
GeneralErrors in code Pin
Eric Sylvestre4-Oct-10 7:55
Eric Sylvestre4-Oct-10 7:55 
Hi,

Just to let you know that you made some mistakes in your code.

Here's the corrected code, just confirm me that it is allright.
    public class CommandReference : Freezable, ICommand
    {
        public static readonly DependencyProperty CommandProperty =
          DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference),
             new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));

        /// <summary>
        /// Gets or sets the command.
        /// </summary>
        /// <value>The command.</value>
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        #region ICommand Members
        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        /// <returns>
        /// true if this command can be executed; otherwise, false.
        /// </returns>
        public bool CanExecute(object parameter)
        {
            return (Command != null) ? Command.CanExecute(parameter) : false;
        }

        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        public void Execute(object parameter) { Command.Execute(parameter); }

        /// <summary>
        /// Occurs when changes occur that affect whether or not the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged;
        private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CommandReference commandRef = d as CommandReference;
            if (commandRef != 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
        /// <summary>
        /// When implemented in a derived class, creates a new instance of the <see cref="T:System.Windows.Freezable"/> derived class.
        /// </summary>
        /// <returns>The new instance.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new CommandReference();
        }
        #endregion
    }
}

GeneralRe: Errors in code Pin
jmix905-Oct-10 12:27
jmix905-Oct-10 12:27 
GeneralRe: Errors in code Pin
Eric Sylvestre5-Oct-10 13:21
Eric Sylvestre5-Oct-10 13:21 
AnswerRe: Errors in code Pin
jmix905-Oct-10 22:13
jmix905-Oct-10 22:13 

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.