Click here to Skip to main content
15,881,733 members
Articles / Desktop Programming / WPF

WPF C# Ribbon Control Library

Rate me:
Please Sign up or sign in to vote.
4.91/5 (97 votes)
25 Jun 2008CPOL26 min read 1.1M   43.2K   439  
A WPF / C# Library for implementing Office 2007 Style Ribbon Interfaces
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;

//------------------------------------------------------------------------------//
//                                                                              //
// Author:  Derek Bartram                                                       //
// Date:    23/01/2008                                                          //
// Version: 1.000                                                               //
// Website: http://www.derek-bartram.co.uk                                      //
// Email:   webmaster@derek-bartram.co.uk                                       //
//                                                                              //
// This code is provided on a free to use and/or modify basis for personal work //
// provided that this banner remains in each of the source code files that is   //
// found in the original source. For any publicically available work (source    //
// and/or binaries 'Derek Bartram' and 'http://www.derek-bartram.co.uk' must be //
// credited in both the user documentation, source code (where applicable), and //
// in the user interface (typically Help > About would be appropiate). Please   //
// also contact myself via the provided email address to let me know where and  //
// what my code is being used for; this helps me provide better solutions for   //
// all.                                                                         //
//                                                                              //
// THIS SOURCE AND/OR COMPILED LIBRARY MUST NOT BE USED FOR COMMERCIAL WORK,    //
// including not-for-profit work, without prior consent.                        //
//                                                                              //
// This agreement overrides any other agreements made by any other parties. By  //
// using, viewing, linking, or compiling the included source or binaries you    //
// agree to the terms and conditions as set out here and in any included (if    //
// applicable) license.txt. For commercial licensing please see the web address //
// above or contact myself via email. Thank you.                                //
//                                                                              //
// Please contact me at the above email for further help, information,          //
// comments, suggestions, licensing, or feature requests. Thank you.            //
//                                                                              //
//                                                                              //
//------------------------------------------------------------------------------//

namespace DNBSoft.WPF.RibbonControl
{
    public class RibbonController
    {
        #region class variables
        private Dictionary<String, RibbonBar> bars = new Dictionary<string, RibbonBar>();
        private Dictionary<RibbonBar, String> barsId = new Dictionary<RibbonBar, String>();
        private Dictionary<String, RibbonSelectionButton> buttons = new Dictionary<string, RibbonSelectionButton>();
        private ListenableList<RibbonBar> listenableBarsList = new ListenableList<RibbonBar>();
        private ListenableList<RibbonSelectionButton> listenableButtonsList = new ListenableList<RibbonSelectionButton>();
        private List<RibbonTabSeperator> tabSeperators = new List<RibbonTabSeperator>();

        private Grid barParent = null;
        private Grid titleParent = null;
        private Window parentWindow = null;
        private Page parentPage = null;
        private StackPanel titleStack = null;

        private RibbonSelectionButton selectedButton = null;

        private RibbonBar currentBar = null;
        private bool firstRun = true;

        private delegate void VoidDelegate();

        private bool hideRibbons = false;
        private RibbonBarPopup lastRibbonPopup = null;

        private RibbonContextController contextController = null;
        #endregion

        #region events
        public event AddToQuickAccessToolbarDelegate AddToQuickAccessToolbarEvent;
        public event CustomiseQuickAccessToolbarDelegate CustomiseQuickAccessToolbarEvent;
        public event ShowQuickAccessToolbarBelowRibbonDelegate ShowQuickAccessToolbarBelowRibbonEvent;
        public event MinimseRibbonDelegate MinimseRibbonEvent;
        #endregion

        #region constructors / loaders
        public RibbonController(Grid barParent, Grid titleParent, Window parentWindow)
        {
            titleStack = new StackPanel();
            titleStack.Orientation = Orientation.Horizontal;
            titleParent.Children.Add(titleStack);
            titleParent.MouseWheel += new MouseWheelEventHandler(titleParent_MouseWheel);

            this.RibbonBarParent = barParent;
            this.TitleBarParent = titleParent;
            this.parentWindow = parentWindow;

            parentWindow.SizeChanged += new SizeChangedEventHandler(parentWindow_SizeChanged);
            parentWindow.Loaded += new RoutedEventHandler(parentWindow_Loaded);
            listenableBarsList.ElementAdded += new ListenableList<RibbonBar>.ElementAddedDelegate(listenableBarsList_ElementAdded);
            listenableBarsList.ElementRemoved += new ListenableList<RibbonBar>.ElementRemovedDelegate(listenableBarsList_ElementRemoved);

            this.MinimseRibbonEvent += new MinimseRibbonDelegate(RibbonController_MinimseRibbonEvent);

            contextController = new RibbonContextController(this);
        }

        private void parentWindow_Loaded(object sender, RoutedEventArgs e)
        {
            forceResizeBiggerUpdate(parentWindow.ActualWidth);
            forceResizeSmallerUpdate(parentWindow.ActualWidth);
        }

        public RibbonController(Grid barParent, Grid titleParent, Page parentPage)
        {
            titleStack = new StackPanel();
            titleStack.Orientation = Orientation.Horizontal;
            titleParent.Children.Add(titleStack);
            titleParent.MouseWheel += new MouseWheelEventHandler(titleParent_MouseWheel);

            this.RibbonBarParent = barParent;
            this.TitleBarParent = titleParent;
            this.parentPage = parentPage;

            parentPage.SizeChanged += new SizeChangedEventHandler(parentWindow_SizeChanged);
            listenableBarsList.ElementAdded += new ListenableList<RibbonBar>.ElementAddedDelegate(listenableBarsList_ElementAdded);
            listenableBarsList.ElementRemoved += new ListenableList<RibbonBar>.ElementRemovedDelegate(listenableBarsList_ElementRemoved);
        }
        #endregion

        #region accessors
        internal Window ParentWindow
        {
            get
            {
                return parentWindow;
            }
        }

        internal Page ParentPage
        {
            get
            {
                return parentPage;
            }
        }

        public bool HideRibbons
        {
            get
            {
                return hideRibbons;
            }
        }

        public RibbonContextController ContextController
        {
            get
            {
                return contextController;
            }
        }
        #endregion

        #region add / remove ribbons
        private void listenableBarsList_ElementRemoved(ListenableList<RibbonBar> sender, ListenableList<RibbonBar>.ElementRemovedEventArgs<RibbonBar> args)
        {
            if (barsId.ContainsKey(args.Item))
            {
                String id = barsId[args.Item];
                RibbonBar bar = bars[id];
                RibbonSelectionButton button = buttons[id];

                if (this.SelectedRibbon == bar)
                {
                    this.SelectedRibbon = listenableBarsList[0];
                }
                titleStack.Children.RemoveAt(args.Index * 2);//button
                titleStack.Children.RemoveAt(args.Index * 2);//seperator

                bars.Remove(id);
                buttons.Remove(id);
                barsId.Remove(bar);
            }
            else
            {
                throw new Exception("RibbonBar not found");
            }
        }

        private void listenableBarsList_ElementAdded(ListenableList<RibbonBar> sender, ListenableList<RibbonBar>.ElementAddedEventArgs<RibbonBar> args)
        {
            String id = Guid.NewGuid().ToString();
            bars.Add(id, args.Item);
            barsId.Add(args.Item, id);

            RibbonSelectionButton rsb = new RibbonSelectionButton(this, args.Item);
            rsb.Text = args.Item.Text;
            buttons.Add(id, rsb);
            titleStack.Children.Insert(args.Index * 2, rsb);
            RibbonTabSeperator separator = new RibbonTabSeperator();
            tabSeperators.Add(separator);
            titleStack.Children.Insert(args.Index * 2 + 1, separator);
            rsb.DoubleClicked += new MouseButtonEventHandler(SelectionButtonDoubleClicked);
            args.Item.TextChanged += new TextChangedEventDelegate(rsb.ribbonBarTextChangedHandler);

            listenableButtonsList.Insert(args.Index, rsb);

            #region First bar added, make selected
            if (bars.Count == 1)
            {
                rsb.IsSelected = true;
                //rsb.unselect();
                barParent.Children.Add(args.Item);
                selectedButton = rsb;
                currentBar = args.Item;

                if (parentWindow.IsLoaded)
                {
                    parentWindow.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, new VoidDelegate(delegate()
                    {
                        forceResizeSmallerUpdate(parentWindow.ActualWidth);
                        forceResizeBiggerUpdate(parentWindow.ActualWidth);

                        RibbonStyleHandler.reStyle();
                    }
                        ));
                }
            }
            #endregion

            #region link
            args.Item.Controller = this;
            #endregion
        }

        internal ListenableList<RibbonSelectionButton> Buttons
        {
            get
            {
                return listenableButtonsList;
            }
        }

        public ListenableList<RibbonBar> Ribbons
        {
            get
            {
                return listenableBarsList;
            }
        }
        #endregion

        #region window resizing
        private void parentWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            #region size ribbons
            if (currentBar != null)
            {
                //Console.WriteLine(parentWindow.Width + " = window width, " + currentBar.ActualWidth);
                if (firstRun)
                {
                    currentBar.resizeSmaller(e.NewSize.Width);
                    currentBar.resizeBigger(e.NewSize.Width);
                    //currentBar.resizeSmaller(e.NewSize.Width);
                    firstRun = false;
                }
                else
                {
                    if (e.PreviousSize.Width >= 300 && e.NewSize.Width < 300)
                    {
                        //hide
                        barParent.MaxHeight = 0;
                        titleParent.MaxHeight = 0;
                    }
                    else if (e.PreviousSize.Width < 300 && e.NewSize.Width >= 300)
                    {
                        //show
                        barParent.MaxHeight = 100;
                        titleParent.MaxHeight = 100;
                    }

                    if (currentBar != null)
                    {
                        try
                        {
                            if (e.PreviousSize.Width + 4 < e.NewSize.Width)
                            {
                                currentBar.resizeBigger(e.NewSize.Width);
                            }
                            else if (e.PreviousSize.Width > e.NewSize.Width)
                            {
                                currentBar.resizeSmaller(e.NewSize.Width);
                            }
                        }
                        catch (Exception err)
                        {
                            Console.WriteLine("Error processing window size change: " + err.ToString());
                        }

                    }
                }
            }
            #endregion

            resizeTabHeadings();
        }

        public void forceResizeSmallerUpdate(double width)
        {
            if (currentBar != null)
            {
                currentBar.resizeSmaller(width);
            }
        }

        public void forceResizeBiggerUpdate(double width)
        {
            if (currentBar != null)
            {
                currentBar.resizeBigger(width);
            }
        }

        public void resizeTabHeadings()
        {
            #region build array
            RibbonSelectionButton[] rButtons = new RibbonSelectionButton[listenableButtonsList.Count];
            for (int i = 0; i < listenableButtonsList.Count; i++)
            {
                rButtons[i] = listenableButtonsList[i];
            }
            #endregion

            #region resize bigger
            bool changedOne = true;

        next_iteration_bigger:
            while (changedOne && headingsWidth() + 110 < parentWindow.ActualWidth)
            {
                Array.Sort(rButtons, new RibbonSelectionButtonComparer());

                for (int i = rButtons.Length - 1; i >= 0; i--)
                {
                    if (rButtons[i].resizeBigger())
                    {
                        titleStack.UpdateLayout();
                        rButtons[i].UpdateLayout();
                        goto next_iteration_bigger;
                    }
                }

                changedOne = false;
            }
            #endregion

            #region resize smaller
            changedOne = true;

        next_iteration_smaller:
            while (changedOne && headingsWidth() + 110 > parentWindow.ActualWidth)
            {
                //Console.WriteLine("new title size : " + (headingsWidth() + 200) + ", parentWindow.ActualWidth = " + parentWindow.ActualWidth);
                Array.Sort(rButtons, new RibbonSelectionButtonComparer());

                for (int i = 0; i < rButtons.Length; i++)
                {
                    if (rButtons[i].resizeSmaller())
                    {
                        titleStack.UpdateLayout();
                        rButtons[i].UpdateLayout();
                        goto next_iteration_smaller;
                    }
                }

                changedOne = false;
            }
            #endregion

            #region show / hide seperators
            bool allFull = true;
            foreach (RibbonSelectionButton r in listenableButtonsList)
            {
                if (!r.IsFullSize)
                {
                    allFull = false;
                }
            }

            if (allFull)
            {
                foreach (RibbonTabSeperator t in tabSeperators)
                {
                    t.IsVisible = false;
                }
            }
            else
            {
                foreach (RibbonTabSeperator t in tabSeperators)
                {
                    t.IsVisible = true;
                }
            }
            #endregion
        }

        private double headingsWidth()
        {
            double width = 0.0;
            foreach (RibbonSelectionButton r in listenableButtonsList)
            {
                width += r.ActualWidth;
            }
            return width + (tabSeperators.Count * 3);
        }
        #endregion

        #region hooks
        public Grid RibbonBarParent
        {
            get
            {
                return barParent;
            }
            set
            {
                barParent = value;
            }
        }

        public Grid TitleBarParent
        {
            get
            {
                return titleParent;
            }
            set
            {
                titleParent = value;
            }
        }
        #endregion

        #region selections
        public RibbonBar SelectedRibbon
        {
            get
            {
                if (selectedButton == null)
                {
                    return null;
                }
                else
                {
                    return selectedButton.RibbonBar;
                }
            }
            set
            {
                if (this.HideRibbons)
                {
                    selectedButton.IsSelected = false;
                    barParent.Children.Clear();

                    #region find corresponding ribbon selection button
                    RibbonSelectionButton button = null;
                    IEnumerator<RibbonSelectionButton> de = Buttons.GetEnumerator();
                    while (de.MoveNext() && button == null)
                    {
                        if (de.Current.RibbonBar == value)
                        {
                            button = de.Current;
                        }
                    }
                    #endregion

                    #region add new
                    selectedButton = button;
                    //selectedButton.select();
                    //barParent.Children.Add(value);
                    currentBar = value;
                    #endregion

                    //must NOT call selected until we are displaying as per normal
                    RibbonBarPopup popup = new RibbonBarPopup(value, parentWindow);
                    if (parentWindow != null)
                    {
                        popup.PlacementTarget = parentWindow;
                    }
                    else if (parentPage != null)
                    {
                        popup.PlacementTarget = parentPage;
                    }
                    popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Relative;
                    popup.VerticalOffset = 52;
                    popup.HorizontalOffset = 0;
                    popup.IsOpen = true;
                    lastRibbonPopup = popup;
                    return;
                }
                else if (Ribbons.Contains(value))
                {
                    #region remove old
                    selectedButton.IsSelected = false;
                    barParent.Children.Clear();
                    #endregion

                    #region find corresponding ribbon selection button
                    RibbonSelectionButton button = null;
                    IEnumerator<RibbonSelectionButton> de = Buttons.GetEnumerator();
                    while (de.MoveNext() && button == null)
                    {
                        if (de.Current.RibbonBar == value)
                        {
                            button = de.Current;
                        }
                    }
                    #endregion

                    #region add new
                    selectedButton = button;
                    selectedButton.IsSelected = true;
                    barParent.Children.Add(value);
                    currentBar = value;
                    #endregion

                    #region resize
                    currentBar.resizeSmaller(parentWindow.ActualWidth);
                    currentBar.resizeBigger(parentWindow.ActualWidth);
                    #endregion
                }
                else
                {
                    throw new Exception("Ribbon not part of Ribbons collection");
                }
            }
        }

        #region minimise ribbon
        private void SelectionButtonDoubleClicked(object sender, MouseButtonEventArgs e)
        {
            if (!hideRibbons)
            {
                hideRibbons = true;
                barParent.Children.Clear();
                if (selectedButton != null)
                {
                    Thread t = new Thread(delegate()
                    {
                        selectedButton.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.ContextIdle, new RibbonEventResources.RefreshDelegate(delegate()
                        {
                            selectedButton.IsSelected = false;
                        }));
                    });
                    t.Start();
                }

            }
            else
            {
                if (lastRibbonPopup != null && lastRibbonPopup.IsOpen == true)
                {
                    lastRibbonPopup.IsOpen = false;
                }
                hideRibbons = false;
                selectedButton.IsSelected = true;
                this.SelectedRibbon = ((RibbonSelectionButton)sender).RibbonBar;
            }
        }

        private void RibbonController_MinimseRibbonEvent(object sender, MinimseRibbonEventArgs args)
        {
            SelectionButtonDoubleClicked(null, null);
        }
        #endregion

        private void titleParent_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (!hideRibbons)
            {
                RibbonBar sBar = this.SelectedRibbon;
                int sBarIndex = listenableBarsList.IndexOf(sBar);

                if (e.Delta > 0 && sBarIndex < listenableBarsList.Count - 1)
                {
                    this.SelectedRibbon = listenableBarsList[sBarIndex + 1];
                }
                else if (e.Delta < 0 && sBarIndex > 0)
                {
                    this.SelectedRibbon = listenableBarsList[sBarIndex - 1];
                }
            }
        }
        #endregion

        #region event firing
        public void fireAddToQuickAccessToolbarDelegate(object sender,
                                                        AddToQuickAccessToolbarEventArgs args)
        {
            if (AddToQuickAccessToolbarEvent != null)
            {
                AddToQuickAccessToolbarEvent(sender, args);
            }
        }

        public void fireCustomiseQuickAccessToolbarDelegate(object sender,
                                                        CustomiseQuickAccessToolbarEventArgs args)
        {
            if (CustomiseQuickAccessToolbarEvent != null)
            {
                CustomiseQuickAccessToolbarEvent(sender, args);
            }
        }

        public void fireShowQuickAccessToolbarBelowRibbonDelegate(object sender,
                                                        ShowQuickAccessToolbarBelowRibbonEventArgs args)
        {
            if (ShowQuickAccessToolbarBelowRibbonEvent != null)
            {
                ShowQuickAccessToolbarBelowRibbonEvent(sender, args);
            }
        }

        public void fireMinimseRibbonDelegate(object sender,
                                                        MinimseRibbonEventArgs args)
        {
            if (MinimseRibbonEvent != null)
            {
                MinimseRibbonEvent(sender, args);
            }
        }
        #endregion

        #region internal classes
        internal class RibbonSelectionButtonComparer : IComparer<RibbonSelectionButton>
        {
            #region IComparer<RibbonSelectionButton> Members

            public int Compare(RibbonSelectionButton x, RibbonSelectionButton y)
            {
                return y.ActualWidth.CompareTo(x.ActualWidth);
            }

            #endregion
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Rail Research UK
United Kingdom United Kingdom
I originally studied for a masters in engineering of software engineering at The University of Birmingham during 2000-2004, of which I received a 2:1. I continued at Birmingham University working with Civil Engineering and Rail Research UK where I am currently in my final year of a 3 year PhD project developing a Computational Intelligent Approach to Railway Intervention Planning. Although my work has a significant focus on railway engineering and associated practices much of my work is with data mining (on SQL Server 2008) and computational intelligence (CI) techniques. My key areas of expertise in CI are clustering algorithms (including Rival Penalised Competitive Learning) and evolutionary algorithms.

Outside of my formal work I enjoy testing the latest technologies such as .NET 3.5 and the many frameworks of which it comprises (mainly WPF). I have several projects on the go including a .NET and DirectX port of Quake 3 and many utility libraries. I also maintain an extensive website coded in Cold Fusion which is regularly updated; more information is available about me there.

Comments and Discussions