Sychronizing RibbonToggleButtons and/or RibbonCheckBoxes

Nov 4, 2008 at 4:38 PM
Have you got any advice for synchronizing the state of two or more RibbonToggleButtons (or similar)? All the sample code I've seen happily puts RibbonToggleButtons on the UI & binds them to RibbonCommands with no reference to where the toggled state is initialised from or how it could be synchronised.

I appreciate that this is an issue with the standard WPF commanding infrastructure, but wondered if you'd come up with any ideas whist building RibbonCommand and the new RibbonXXX controls, given that the whole ribbon architecture is so command centric (and there is the QuickAccessToolbar).

The most obvious manifestation of this problem is toggling the state of a "Bold" button in a text editor depending on whether the text under the cursor is currently bold. I notice that the CodePlex WYSIWYG HTML editor (that I'm typing in now) can do this, but it's hard/impossible to accomplish this within the WPF commanding architecture. It's also a particular difficulty when using loosly coupled architectures (like Prism) which use commands as the mechanism for decoupling the UI.

If you've not got any ideas or recommendations, do you know if anyone on the WPF team is thinking about this problem for the next release?

Thanks,
James.
Nov 4, 2008 at 11:44 PM
The way we implemented this in our own Ribbon implementation (Divelements) is with the command parameter. When you handle CanExecute, you then have a way to feed parameters back to the ribbon control who is asking. The most common case (and default) uses CheckableCommandParameter, which simply has an IsChecked property. Here's an example of a CanExecute handler for your example, the Bold command.

ICheckableCommandParameter checkable = e.Parameter as ICheckableCommandParameter;
if (checkable != null)
{
 if (e.Command == EditingCommands.ToggleBold)
 {
  object fontWeightValue = textBox.Selection.GetPropertyValue(FontWeightProperty);
  if (fontWeightValue != DependencyProperty.UnsetValue)
  {
   FontWeight fontWeight = (FontWeight)fontWeightValue;
   checkable.IsChecked = fontWeight == FontWeights.Bold;
  }
 }
}

This works very well and ensures that all ribbon controls bound to the Bold command automatically set their Checked state at the same time they set their enabled state. Please feel free to check out SandRibbon, and Microsoft, I recommend that in your own ribbon efforts you investigate a similar method of enabling this everyday requirement.
Nov 5, 2008 at 10:04 AM
Thanks. I've quickly tried that in the HOL (exercise 2) and it works quite well. I too encourage the WPF team to include something like that in the Ribbon (or preferably in the base platform).

In case it helps anyone else, here's what I did:

Add an interface & class for the command parameter:

using System.ComponentModel;
namespace CheckbookManager
{
    public interface ICheckableCommandParameter : INotifyPropertyChanged
    {
        object Parameter { get; set; }
        bool IsChecked { get; set; }
    }

    public class CheckableCommandParameter : ICheckableCommandParameter
    {
        private object parameter;
        private bool isChecked;

        public object Parameter
        {
            get { return parameter; }
            set
            {
                if (parameter == value)
                    return;

                parameter = value;
                OnPropertyChanged("Parameter");
            }
        }

        public bool IsChecked
        {
            get { return isChecked; }
            set
            {
                if (isChecked == value)
                    return;

                isChecked = value;
                OnPropertyChanged("IsChecked");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(PropertyChangedEventArgs ea)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, ea);
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}
I then changed the CommandBinding for the Calculator command in Main.xaml:
 <CommandBinding Command="me:AppCommands.Calculator"
                        Executed="OnToggleCalculator"
                        CanExecute="ToggleCalculator_CanExecute" />
...and added the handlers to Main.xaml.cs:

        private bool calculatorVisible = false;

        private void OnToggleCalculator(object sender, ExecutedRoutedEventArgs e)
        {
            calculatorVisible = !calculatorVisible;
        }

        private void ToggleCalculator_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            CheckableCommandParameter param = e.Parameter as CheckableCommandParameter;
            if (param != null)
            {
                param.IsChecked = calculatorVisible;
            }

            e.CanExecute = true;
        }
I then added the command parameter to the RibbonButton and bound the IsChecked state of the buttons to the IsChecked state of the command parameter:

                        <r:RibbonToggleButton Command="me:AppCommands.Calculator"
                                              IsChecked="{Binding RelativeSource={RelativeSource Self}, Path=CommandParameter.IsChecked}">
                            <r:RibbonToggleButton.CommandParameter>
                                <me:CheckableCommandParameter />
                            </r:RibbonToggleButton.CommandParameter>
                        </r:RibbonToggleButton>

I added the same button to the QuickAccessToolbar, and both button states stayed in sync!