Storing and retrieving WPF RibbonWindow settings (including the usercustomizable QuickAccessToolbar)

When you’re creating a desktop application you’d probably want to store some user settings.
Especially when using the Ribbon inside your application, there are several things (like RibbonIsMinimized and ShowQuickAccessToolBarOnTop) that users can set and want to keep even if the application en closed and restarted.

There are several ways to store these user settings, and you can find some methods if you search the internet. But the biggest challenge is to store and retrieve the Quick Access Toolbar content which the user can change.

Defining

First the storage of the settings. I use the application user properties which are set through the project settings.
We define the following settings:

  • FirstRun (bool)
  • WindowSize (System.Windows.Size)
  • WindowLocation (System.Windows.Point)
  • WindowState (System.Windows.WindowState)
  • RibbonIsMinimized (bool)
  • ShowQuickAccessToolbarOnTop (bool)
  • RibbonQuickAccessToolbar (string)

ProjectSettings

If you can’t find the right type inside the type combobox, just select [Browse…] to select it from available DLL’s. Point and Size are found in WindowsBase, and WindowState is in PresentationFramework.

Storing

Next step is storing the settings. I leave the QuickAccessToolbar out for this moment. I’ll add it later.

The storing is taking place in de code behind of the Shell, which is the main window of the current application. It is not mandatory to do this in the code behind, but for the moment it is the easiest.

I store the settings when the window is closing.

protected override void OnClosing(CancelEventArgs e)
{
    Properties.Settings.Default.FirstRun = false;
    Properties.Settings.Default.WindowSize = new Size(Width, Height);
    Properties.Settings.Default.WindowLocation = new Point(Left, Top);
    Properties.Settings.Default.WindowState = WindowState;
    Properties.Settings.Default.RibbonIsMinimized = RibbonMenu.IsMinimized;
    Properties.Settings.Default.ShowQuickAccessToolBarOnTop = RibbonMenu.ShowQuickAccessToolBarOnTop;
    Properties.Settings.Default.RibbonQuickAccessToolBar = XamlWriter.Save(buttons);

    Properties.Settings.Default.Save();

    base.OnClosing(e);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

The RibbonMenu is referencing to the <Ribbon x:Name=”RibbonMenu” > inside the Shell.XAML.

Width, Height, Left, Top and WindowState are properties of the WPF (Ribbon)Window.

Retrieving

And when the application is starting again, you just have to get these settings and set them back. The best moment to do this is in the OnInitialized of the Shell.

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);

    if (!Properties.Settings.Default.FirstRun)
    {
        Width = Properties.Settings.Default.WindowSize.Width;
        Height = Properties.Settings.Default.WindowSize.Height;
        Left = Properties.Settings.Default.WindowLocation.X;
        Top = Properties.Settings.Default.WindowLocation.Y;
        WindowState = Properties.Settings.Default.WindowState;
        RibbonMenu.IsMinimized = Properties.Settings.Default.RibbonIsMinimized;
        RibbonMenu.ShowQuickAccessToolBarOnTop = Properties.Settings.Default.ShowQuickAccessToolBarOnTop;
    }
}

So far the easy part.

QuickAccessToolbar

Storing and retrieving the QuickAccessToolbar is a bit more complicated. Because this can’t just be serialized.
First I tried and used XamlWriter and XamlReader to try serialize the items inside the QuickAccessToolbar. Although storing it worked fine (it made a huge xml file), when trying to load the Xaml, I soon experienced the problem that the XamlWriter also serialized the bindings (with it’s content) i set on the command property. Unfortunately these bindings are DelegateCommands from Prism, which can’t be constructed through deserialization.

The solution is a bit complex, but it works just fine. I created my own entities for storing and retrieving the items inside the QuickAccessToolbar.

[Serializable]
public class QuickAccessToolbarButtonCollection : List<QuickAccessToolbarButton> { }

[Serializable]
public class QuickAccessToolbarButton
{
    public ImageSource LargeImageSource { get; set; }
    public ImageSource SmallImageSource { get; set; }
    public string Label { get; set; }
    public object QuickAccessToolBarId { get; set; }
    public object ToolTip { get; set; }
    public string ToolTipDescription { get; set; }
    public string KeyTip { get; set; }
    public Binding CommandBinding { get; set; }
}

Off course you can add more properties if you’re setting more attributes on the RibbonButton. These properties were everything I’ve used.

Next step is creating and storing the QuickAccessToolbar items.

#region Create the QuickAccessToolbarButtonCollection
QuickAccessToolbarButtonCollection buttons = new QuickAccessToolbarButtonCollection();

foreach (RibbonButton rButton in RibbonMenu.QuickAccessToolBar.Items)
{
    if (rButton.KeyTip != "S") // Don't include the savebutton!
    {
        QuickAccessToolbarButton qaButton = new QuickAccessToolbarButton()
        {
            Label = rButton.Label,
            KeyTip = rButton.KeyTip,
            LargeImageSource = rButton.LargeImageSource,
            SmallImageSource = rButton.SmallImageSource,
            ToolTip = rButton.ToolTip,
            ToolTipDescription = rButton.ToolTipDescription,
            QuickAccessToolBarId = rButton.QuickAccessToolBarId,
            CommandBinding = BindingOperations.GetBinding(rButton, RibbonButton.CommandProperty)
        };
        buttons.Add(qaButton);
    }
}
#endregion

And when the collection filled, I use the XamlWriter to set this data inside a user setting

Properties.Settings.Default.RibbonQuickAccessToolBar = XamlWriter.Save(buttons);

The reason I’m not using a conventional XMLSerializer is because the XamlWriter can serialize bindings.

Last step is to read the stored setting. For this I use the XamlReader to deserialize back to our custom entity.

#region Load the QuickAccessToolbarButtonCollection
if (!string.IsNullOrEmpty(Properties.Settings.Default.RibbonQuickAccessToolBar))
{
    QuickAccessToolbarButtonCollection buttons = null;
    using (StringReader stringReader = new StringReader(Properties.Settings.Default.RibbonQuickAccessToolBar))
    {
        XmlReader xmlReader = XmlReader.Create(stringReader);
        buttons = (QuickAccessToolbarButtonCollection)XamlReader.Load(xmlReader);
        xmlReader.Close();
    }

    if (buttons != null)
    {
        for (int i = RibbonMenu.QuickAccessToolBar.Items.Count - 1; i >= 0; i--)
        {
            RibbonButton button = (RibbonButton)RibbonMenu.QuickAccessToolBar.Items.GetItemAt(i);
            if (button.KeyTip != "S") // Don't delete the savebutton!
                RibbonMenu.QuickAccessToolBar.Items.RemoveAt(i);
        }

        foreach (QuickAccessToolbarButton qaButton in buttons)
        {
            RibbonButton rButton = new RibbonButton()
            {
                Label = qaButton.Label,
                KeyTip = qaButton.KeyTip,
                LargeImageSource = qaButton.LargeImageSource,
                SmallImageSource = qaButton.SmallImageSource,
                ToolTip = qaButton.ToolTip,
                ToolTipDescription = qaButton.ToolTipDescription,
                QuickAccessToolBarId = qaButton.QuickAccessToolBarId
            };
            if (qaButton.CommandBinding != null)
                SetBinding(RibbonButton.CommandProperty, qaButton.CommandBinding);

            RibbonMenu.QuickAccessToolBar.Items.Add(rButton);
        }
    }
}
#endregion

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Check explicitly if the commandbinding are null, else you get exceptions when initializing the Shell.

 

Overview

Below the complete two methods.

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);

    if (!Properties.Settings.Default.FirstRun)
    {
        Width = Properties.Settings.Default.WindowSize.Width;
        Height = Properties.Settings.Default.WindowSize.Height;
        Left = Properties.Settings.Default.WindowLocation.X;
        Top = Properties.Settings.Default.WindowLocation.Y;
        WindowState = Properties.Settings.Default.WindowState;
        RibbonMenu.IsMinimized = Properties.Settings.Default.RibbonIsMinimized;
        RibbonMenu.ShowQuickAccessToolBarOnTop = Properties.Settings.Default.ShowQuickAccessToolBarOnTop;

        #region Load the QuickAccessToolbarButtonCollection
        if (!string.IsNullOrEmpty(Properties.Settings.Default.RibbonQuickAccessToolBar))
        {
            QuickAccessToolbarButtonCollection buttons = null;
            using (StringReader stringReader = new StringReader(Properties.Settings.Default.RibbonQuickAccessToolBar))
            {
                XmlReader xmlReader = XmlReader.Create(stringReader);
                buttons = (QuickAccessToolbarButtonCollection)XamlReader.Load(xmlReader);
                xmlReader.Close();
            }

            if (buttons != null)
            {
                for (int i = RibbonMenu.QuickAccessToolBar.Items.Count - 1; i >= 0; i--)
                {
                    RibbonButton button = (RibbonButton)RibbonMenu.QuickAccessToolBar.Items.GetItemAt(i);
                    if (button.KeyTip != "S") // Don't delete the savebutton!
                        RibbonMenu.QuickAccessToolBar.Items.RemoveAt(i);
                }

                foreach (QuickAccessToolbarButton qaButton in buttons)
                {
                    RibbonButton rButton = new RibbonButton()
                    {
                        Label = qaButton.Label,
                        KeyTip = qaButton.KeyTip,
                        LargeImageSource = qaButton.LargeImageSource,
                        SmallImageSource = qaButton.SmallImageSource,
                        ToolTip = qaButton.ToolTip,
                        ToolTipDescription = qaButton.ToolTipDescription,
                        QuickAccessToolBarId = qaButton.QuickAccessToolBarId
                    };
                    if (qaButton.CommandBinding != null)
                        SetBinding(RibbonButton.CommandProperty, qaButton.CommandBinding);

                    RibbonMenu.QuickAccessToolBar.Items.Add(rButton);
                }
            }
        }
        #endregion
    }
}

protected override void OnClosing(CancelEventArgs e)
{
    #region Create the QuickAccessToolbarButtonCollection
    QuickAccessToolbarButtonCollection buttons = new QuickAccessToolbarButtonCollection();

    foreach (RibbonButton rButton in RibbonMenu.QuickAccessToolBar.Items)
    {
        if (rButton.KeyTip != "S") // Don't include the savebutton!
        {
            QuickAccessToolbarButton qaButton = new QuickAccessToolbarButton()
            {
                Label = rButton.Label,
                KeyTip = rButton.KeyTip,
                LargeImageSource = rButton.LargeImageSource,
                SmallImageSource = rButton.SmallImageSource,
                ToolTip = rButton.ToolTip,
                ToolTipDescription = rButton.ToolTipDescription,
                QuickAccessToolBarId = rButton.QuickAccessToolBarId,
                CommandBinding = BindingOperations.GetBinding(rButton, RibbonButton.CommandProperty)
            };
            buttons.Add(qaButton);
        }
    }
    #endregion

    Properties.Settings.Default.FirstRun = false;
    Properties.Settings.Default.WindowSize = new Size(Width, Height);
    Properties.Settings.Default.WindowLocation = new Point(Left, Top);
    Properties.Settings.Default.WindowState = WindowState;
    Properties.Settings.Default.RibbonIsMinimized = RibbonMenu.IsMinimized;
    Properties.Settings.Default.ShowQuickAccessToolBarOnTop = RibbonMenu.ShowQuickAccessToolBarOnTop;
    Properties.Settings.Default.RibbonQuickAccessToolBar = XamlWriter.Save(buttons);

    Properties.Settings.Default.Save();

    base.OnClosing(e);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Enjoy!

You may also like...

4 Responses

  1. John Hagan says:

    Great article on persisting WPF RibbonWindow settings–the very best explanation and examples. Thank you so much for taking the time to share it.

    For what it’s worth, I don’t believe the [Serializable] is necessary (or applicable) when using XamlWriter to serialize data.

  2. leo says:

    Hi Andries, Thanks for sharing the solution, but i keep getting err “cannot serialize a generic type DelegateCommand” at XamlWriter.Save(buttons); And found it’s related to QuickAccessToolBarId property. However, just cannot solve it, and don’t know what i’m missing. Could you please help?

  3. AvdMeulen says:

    @leo
    You probably have a DelegateCommand bound to one of the RibbonButtons’s Command property.
    Unfortunatly, the XamlWriter cannot serialize generics.
    The only way I can think of at the moment is to replace the DelegateCommand with an own ICommand class.
    That way when loading the application, the Deserializer can instantiate your command.

  4. Globus000 says:

    Hi Andries!
    First of all – thank you for the post! Very useful!
    Unfortunately it doesnt work for me 🙁 For some unknow reasons XamlWriter cannot serialize these two classes. I’ve worked around the custom collection by using ArrayList instead. But as for QuickAccessToolbarButton it still fails. For testing purposes I’ve tried to comment all the fields except string-s. But still no luck.

Leave a Reply

Your email address will not be published. Required fields are marked *