Click here to Skip to main content
15,885,244 members
Articles / Desktop Programming / Windows Forms

A Professional Ribbon You Will Use (Now with orb!)

Rate me:
Please Sign up or sign in to vote.
4.96/5 (573 votes)
12 Jun 2012Ms-PL6 min read 2.3M   141.7K   1.4K  
A serious project on an Office-like Ribbon control
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.ComponentModel;

namespace System.Windows.Forms
{
    public class RibbonOrbDropDown
        : RibbonPopup
    {
        #region Fields
        internal RibbonOrbMenuItem LastPoppedMenuItem;
        private Rectangle designerSelectedBounds;
        private int glyphGap = 3;
        private Padding _contentMargin;
        private Ribbon _ribbon;
        private RibbonItemCollection _menuItems;
        private RibbonItemCollection _recentItems;
        private RibbonItemCollection _optionItems;
        private RibbonSensor _sensor;
        private int _optionsPadding;
        #endregion

        #region Ctor

        internal RibbonOrbDropDown(Ribbon ribbon)
            : base()
        {
            DoubleBuffered = true;
            _ribbon = ribbon;
            _menuItems = new RibbonItemCollection();
            _recentItems = new RibbonItemCollection();
            _optionItems = new RibbonItemCollection();

            _menuItems.SetOwner(Ribbon);
            _recentItems.SetOwner(Ribbon);
            _optionItems.SetOwner(Ribbon);

            _optionsPadding = 6;
            Size = new System.Drawing.Size(527, 447);
            
        }

        ~RibbonOrbDropDown()
        {
            if (_sensor != null)
            {
                _sensor.Dispose();
            }
        }

        #endregion

        #region Props

        /// <summary>
        /// Gets all items involved in the dropdown
        /// </summary>
        internal List<RibbonItem> AllItems
        {
            get
            {
                List<RibbonItem> lst = new List<RibbonItem>();
                lst.AddRange(MenuItems); lst.AddRange(RecentItems); lst.AddRange(OptionItems);
                return lst;
            }
        }

        /// <summary>
        /// Gets the margin of the content bounds
        /// </summary>
        [Browsable(false)]
        public Padding ContentMargin
        {
            get 
            {
                if (_contentMargin.Size.IsEmpty)
                {
                    _contentMargin = new Padding(6, 17, 6, 29);
                }

                return _contentMargin;
            }
        }

        /// <summary>
        /// Gets the bounds of the content (where menu buttons are)
        /// </summary>
        [Browsable(false)]
        public Rectangle ContentBounds
        {
            get 
            { 
                return Rectangle.FromLTRB(ContentMargin.Left, ContentMargin.Top, 
                    ClientRectangle.Right - ContentMargin.Right, 
                    ClientRectangle.Bottom - ContentMargin.Bottom);
            }
        }

        /// <summary>
        /// Gets the bounds of the content part that contains the buttons on the left
        /// </summary>
        [Browsable(false)]
        public Rectangle ContentButtonsBounds
        {
            get
            {
                Rectangle r = ContentBounds;
                r.Width = 150;
                return r;
            }
        }

        /// <summary>
        /// Gets the bounds fo the content part that contains the recent-item list
        /// </summary>
        [Browsable(false)]
        public Rectangle ContentRecentItemsBounds
        {
            get
            {
                Rectangle r = ContentBounds;
                r.Width -= 150;
                r.X += 150;
                return r;
            }
        }

        /// <summary>
        /// Gets if currently on design mode
        /// </summary>
        private bool RibbonInDesignMode
        {
            get { return RibbonDesigner.Current != null; }
        }

        /// <summary>
        /// Gets the collection of items shown in the menu area
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public RibbonItemCollection MenuItems
        {
            get { return _menuItems; }
        }

        /// <summary>
        /// Gets the collection of items shown in the options area (bottom)
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public RibbonItemCollection OptionItems
        {
            get { return _optionItems; }
        }

        [DefaultValue(6)]
        [Description("Spacing between option buttons (those on the bottom)")]
        public int OptionItemsPadding
        {
            get { return _optionsPadding; }
            set { _optionsPadding = value; }
        }

        /// <summary>
        /// Gets the collection of items shown in the recent items area
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public RibbonItemCollection RecentItems
        {
            get { return _recentItems; }
        }

        /// <summary>
        /// Gets the ribbon that owns this dropdown
        /// </summary>
        [Browsable(false)]
        public Ribbon Ribbon
        {
            get { return _ribbon; }
        }

        /// <summary>
        /// Gets the sensor of the dropdown
        /// </summary>
        [Browsable(false)]
        public RibbonSensor Sensor
        {
            get { return _sensor; }
        }

        /// <summary>
        /// Gets the bounds of the glyph
        /// </summary>
        internal Rectangle ButtonsGlyphBounds
        {
            get 
            {
                Size s = new Size(50, 18);
                Rectangle rf = ContentButtonsBounds;
                Rectangle r = new Rectangle(rf.Left + (rf.Width - s.Width * 2) / 2, rf.Top + glyphGap, s.Width, s.Height);
                
                if (MenuItems.Count > 0)
                {
                    r.Y = MenuItems[MenuItems.Count - 1].Bounds.Bottom + glyphGap;
                }

                return r;
            }
        }

        /// <summary>
        /// Gets the bounds of the glyph
        /// </summary>
        internal Rectangle ButtonsSeparatorGlyphBounds
        {
            get
            {
                Size s = new Size(18, 18);

                Rectangle r = ButtonsGlyphBounds;

                r.X = r.Right + glyphGap;

                return r;
            }
        }

        /// <summary>
        /// Gets the bounds of the recent items add glyph
        /// </summary>
        internal Rectangle RecentGlyphBounds
        {
            get
            {
                Size s = new Size(50, 18);
                Rectangle rf = ContentRecentItemsBounds;
                Rectangle r = new Rectangle(rf.Left + glyphGap, rf.Top + glyphGap, s.Width, s.Height);

                if (RecentItems.Count > 0)
                {
                    r.Y = RecentItems[RecentItems.Count - 1].Bounds.Bottom + glyphGap;
                }

                return r;
            }
        }

        /// <summary>
        /// Gets the bounds of the option items add glyph
        /// </summary>
        internal Rectangle OptionGlyphBounds
        {
            get
            {
                Size s = new Size(50, 18);
                Rectangle rf = ContentBounds;
                Rectangle r = new Rectangle(rf.Right - s.Width, rf.Bottom + glyphGap, s.Width, s.Height);

                if (OptionItems.Count > 0)
                {
                    r.X = OptionItems[OptionItems.Count - 1].Bounds.Left - s.Width - glyphGap;
                }

                return r;
            }
        }

        #endregion

        #region Methods

        internal void HandleDesignerItemRemoved(RibbonItem item)
        {
            if (MenuItems.Contains(item))
            {
                MenuItems.Remove(item);
            }
            else if (RecentItems.Contains(item))
            {
                RecentItems.Remove(item);
            }
            else if (OptionItems.Contains(item))
            {
                OptionItems.Remove(item);
            }

            OnRegionsChanged();
        }

        /// <summary>
        /// Gets the height that a separator should be on the DropDown
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private int SeparatorHeight(RibbonSeparator s)
        {
            if (!string.IsNullOrEmpty(s.Text))
            {
                return 20;
            }
            else
            {
                return 3;
            }
        }

        /// <summary>
        /// Updates the regions and bounds of items
        /// </summary>
        private void UpdateRegions()
        {
            int curtop = 0;
            int curright = 0;
            int menuItemHeight = 44;
            int recentHeight = 22;
            Rectangle rcontent = ContentBounds;
            Rectangle rbuttons = ContentButtonsBounds;
            Rectangle rrecent = ContentRecentItemsBounds;
            int mbuttons = 1; //margin
            int mrecent = 1;  //margin
            int buttonsHeight = 0;
            int recentsHeight = 0;

            foreach (RibbonItem item in AllItems)
            {
                item.SetSizeMode(RibbonElementSizeMode.DropDown);
                item.SetCanvas(this);
            }

            #region Menu Items

            curtop = rcontent.Top + 1;

            foreach (RibbonItem item in MenuItems)
            {
                Rectangle ritem = new Rectangle(rbuttons.Left + mbuttons, curtop, rbuttons.Width - mbuttons * 2, menuItemHeight);

                if (item is RibbonSeparator) ritem.Height = SeparatorHeight(item as RibbonSeparator);

                item.SetBounds(ritem);

                curtop += ritem.Height;
            }

            buttonsHeight = curtop - rcontent.Top + 1;

            #endregion

            #region Recent List

            curtop = rbuttons.Top;

            foreach (RibbonItem item in RecentItems)
            {
                Rectangle ritem = new Rectangle(rrecent.Left + mrecent, curtop, rrecent.Width - mrecent * 2, recentHeight);

                if (item is RibbonSeparator) ritem.Height = SeparatorHeight(item as RibbonSeparator);

                item.SetBounds(ritem);

                curtop += ritem.Height;
            }

            recentsHeight = curtop - rbuttons.Top;

            #endregion

            #region Set size

            int actualHeight = Math.Max(buttonsHeight, recentsHeight);

            if (RibbonDesigner.Current != null)
            {
                actualHeight += ButtonsGlyphBounds.Height + glyphGap * 2;
            }

            Height = actualHeight + ContentMargin.Vertical;
            rcontent = ContentBounds;

            #endregion

            #region Option buttons

            curright = ClientSize.Width - ContentMargin.Right;

            using (Graphics g = CreateGraphics())
            {
                foreach (RibbonItem item in OptionItems)
                {
                    Size s = item.MeasureSize(this, new RibbonElementMeasureSizeEventArgs(g, RibbonElementSizeMode.DropDown));
                    curtop = rcontent.Bottom + (ContentMargin.Bottom - s.Height) / 2;
                    item.SetBounds(new Rectangle(new Point(curright - s.Width, curtop), s));
                    curright = item.Bounds.Left - OptionItemsPadding;
                } 
            }

            #endregion
        }

        /// <summary>
        /// Refreshes the sensor
        /// </summary>
        private void UpdateSensor()
        {
            if (_sensor != null)
            {
                _sensor.Dispose();
            }

            _sensor = new RibbonSensor(this, Ribbon, AllItems);
        }

        /// <summary>
        /// Updates all areas and bounds of items
        /// </summary>
        internal void OnRegionsChanged()
        {
            UpdateRegions();
            UpdateSensor();
            UpdateDesignerSelectedBounds();
            Invalidate();
        }

        /// <summary>
        /// Selects the specified item on the designer
        /// </summary>
        /// <param name="item"></param>
        internal void SelectOnDesigner(RibbonItem item)
        {
            if (RibbonDesigner.Current != null)
            {
                RibbonDesigner.Current.SelectedElement = item;
                UpdateDesignerSelectedBounds();
                Invalidate();
            }
        }

        internal void UpdateDesignerSelectedBounds()
        {
            designerSelectedBounds = Rectangle.Empty;

            if (RibbonInDesignMode)
            {
                RibbonItem item = RibbonDesigner.Current.SelectedElement as RibbonItem;

                if (item != null && AllItems.Contains(item))
                {
                    designerSelectedBounds = item.Bounds;
                }
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (RibbonInDesignMode)
            {
                #region DesignMode clicks
                if (ContentBounds.Contains(e.Location))
                {
                    if (ContentButtonsBounds.Contains(e.Location))
                    {
                        foreach (RibbonItem item in MenuItems)
                        {
                            if (item.Bounds.Contains(e.Location))
                            {
                                SelectOnDesigner(item);
                                break;
                            }
                        }
                    }
                    else if (ContentRecentItemsBounds.Contains(e.Location))
                    {
                        foreach (RibbonItem item in RecentItems)
                        {
                            if (item.Bounds.Contains(e.Location))
                            {
                                SelectOnDesigner(item);
                                break;
                            }
                        }
                    }
                }
                if (ButtonsGlyphBounds.Contains(e.Location))
                {
                    RibbonDesigner.Current.CreteOrbMenuItem(typeof(RibbonOrbMenuItem));
                }
                else if (ButtonsSeparatorGlyphBounds.Contains(e.Location))
                {
                    RibbonDesigner.Current.CreteOrbMenuItem(typeof(RibbonSeparator));
                }
                else if (RecentGlyphBounds.Contains(e.Location))
                {
                    RibbonDesigner.Current.CreteOrbRecentItem(typeof(RibbonOrbRecentItem));
                }
                else if (OptionGlyphBounds.Contains(e.Location))
                {
                    RibbonDesigner.Current.CreteOrbOptionItem(typeof(RibbonOrbOptionButton));
                }
                else
                {
                    foreach (RibbonItem item in OptionItems)
                    {
                        if (item.Bounds.Contains(e.Location))
                        {
                            SelectOnDesigner(item);
                            break;
                        }
                    }
                } 
                #endregion
            }

        }

        protected override void OnOpening(System.ComponentModel.CancelEventArgs e)
        {
            base.OnOpening(e);

            UpdateRegions();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            Ribbon.Renderer.OnRenderOrbDropDownBackground(
                new RibbonOrbDropDownEventArgs(Ribbon, this, e.Graphics, e.ClipRectangle));

            foreach (RibbonItem item in AllItems)
            {
                item.OnPaint(this, new RibbonElementPaintEventArgs(e.ClipRectangle, e.Graphics, RibbonElementSizeMode.DropDown));
            }

            if (RibbonInDesignMode)
            {
                using (SolidBrush b = new SolidBrush(Color.FromArgb(50, Color.Blue)))
                {
                    e.Graphics.FillRectangle(b, ButtonsGlyphBounds);
                    e.Graphics.FillRectangle(b, RecentGlyphBounds);
                    e.Graphics.FillRectangle(b, OptionGlyphBounds);
                    e.Graphics.FillRectangle(b, ButtonsSeparatorGlyphBounds);
                }

                using (StringFormat sf = new StringFormat())
                {
                    sf.Alignment = StringAlignment.Center;
                    sf.LineAlignment = StringAlignment.Center;
                    sf.Trimming = StringTrimming.None;
                    e.Graphics.DrawString("+", Font, Brushes.White, ButtonsGlyphBounds, sf);
                    e.Graphics.DrawString("+", Font, Brushes.White, RecentGlyphBounds, sf);
                    e.Graphics.DrawString("+", Font, Brushes.White, OptionGlyphBounds, sf);
                    e.Graphics.DrawString("---", Font, Brushes.White, ButtonsSeparatorGlyphBounds, sf);
                }

                using (Pen p = new Pen(Color.Black))
                {
                    p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                    e.Graphics.DrawRectangle(p, designerSelectedBounds);
                }

                //e.Graphics.DrawString("Press ESC to Hide", Font, Brushes.Black, Width - 100f, 2f);
            }
        }

        protected override void OnClosing(ToolStripDropDownClosingEventArgs e)
        {
            base.OnClosing(e);

            if (RibbonInDesignMode)
            {
                RibbonItem item = RibbonDesigner.Current.SelectedElement as RibbonItem;
                e.Cancel = item != null && 
                    (MenuItems.Contains(item) || RecentItems.Contains(item) || OptionItems.Contains(item)) && 
                    e.CloseReason != ToolStripDropDownCloseReason.Keyboard &&
                    e.CloseReason != ToolStripDropDownCloseReason.AppClicked;

            }
            else
            {
                Console.WriteLine(e.CloseReason.ToString());
            }
        }

        protected override void OnClosed(EventArgs e)
        {
            Ribbon.OrbPressed = false;
            Ribbon.OrbSelected = false;
            base.OnClosed(e);
        }

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

            UpdateSensor();
        }

        #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 Microsoft Public License (Ms-PL)


Written By
Product Manager
United States United States
- I've been programming Windows and Web apps since 1997.
- My greatest concern nowadays is product, user interface, and usability.
- TypeScript / React expert

@geeksplainer

Comments and Discussions