Click here to Skip to main content
Click here to Skip to main content
Go to top

Cool Vista-style Menu

, 3 Jan 2009
Rate this:
Please Sign up or sign in to vote.
Cool Vista style menu in your .NET applications
Vista menu control

Introduction

This is the second release of this control, so my previous article was a bit poor, I decided to update it too. I based the development of this control on Vista start menu, but as you can see in the image above, it can be easily customized to support other styles, themes, etc. See demo project for more details (mac style menus, applying themes at runtime, etc.).

Using the Code

Using this control is extremely easy. Just add it to your toolbox, then drag & drop on your form.

Next, populate the menu control, adding menu items. There are several overloaded methods you can use to achieve this:

  • public VistaMenuItem Add(string sText);
  • public VistaMenuItem Add(string sText, string sDescription);
  • public VistaMenuItem Add(string sText, string sDescription, Image img);
  • public VistaMenuItem Add(string sText, Image img);
  • public void Add(VistaMenuItem btn);

So, if you want rich menu items, you should use the third method. Here is a simple code to add some items:

   for(int idx = 0; idx < 5; idx++)
      vmcMenu.MenuItems.Add(
              "Item " + idx,
	      "Description " + idx,
               img
       );

Structure

The image below shows different elements of a menu control. Menu items are rich-rendered, that means, they have an image representation, caption to identify them, and a short description. Every menu item is limited by a separator. They can be flat, 3D, and in this release of a control, you can also specify not to render them at all. This feature will be presented later on. With a menu sidebar you can group different menus, using distinct name, and also provide an icon that will be rendered next to sidebar caption.

Menu panel is rendered using gradient colors as shown in the image.
The control implements a lot of properties related to menu's appearance, like start and end gradient colors, inner/ outer border colors, sidebar gradient colors:

  • public Color MenuStartColor 
  • public Color MenuEndColor 
  • public Color MenuInnerBorderColor 
  • public Color MenuOuterBorderColor 
  • public Color SideBarStartGradient 
  • public Color SideBarEndGradient 

Vista menu control is also able to render background image. You can set the image alignment, defined by ContentAlignment enumeration, and then DrawBackImage is invoked to draw the image.

 private void DrawBackImage(
            Graphics gfx,
            Rectangle rc
            )
        {
            if (m_bmpBackImage != null)
            {
                int lW =  m_bmpBackImage.Width;
                int lH = m_bmpBackImage.Height;
                Rectangle rcImage = new Rectangle(
                    0,
                    0,
                    lW,
                    lH
                    );

                switch (m_ImageAlign)
                {
                    case ContentAlignment.BottomCenter:
                        rcImage.X = rc.Width / 2 - lW / 2;
                        rcImage.Y = rc.Height - lH - 2;
                        break;
                    case ContentAlignment.BottomLeft:
                        rcImage.X = rc.Left + 2;
                        rcImage.Y = rc.Height - lH - 2;
                        break;
                    case ContentAlignment.BottomRight:
                        rcImage.X = rc.Right - lW -  2;
                        rcImage.Y = rc.Height - lH - 2;
                        break;
                    case ContentAlignment.MiddleCenter:
                        rcImage.X = rc.Width / 2 - lW / 2;
                        rcImage.Y = rc.Height / 2 - lH / 2;
                        break;
                    case ContentAlignment.MiddleLeft:
                        rcImage.X = rc.Left + 2;
                        rcImage.Y = rc.Height / 2 - lH / 2;
                        break;
                    case ContentAlignment.MiddleRight:
                        rcImage.X = rc.Right - lW - 2;
                        rcImage.Y = rc.Height / 2 - lH / 2;
                        break;
                    case ContentAlignment.TopCenter:
                        rcImage.X = rc.Width / 2 - lW / 2;
                        rcImage.Y = rc.Top + 2;
                        break;
                    case ContentAlignment.TopLeft:
                        rcImage.X = rc.Left + 2;
                        rcImage.Y = rc.Top + 2;
                        break;
                    case ContentAlignment.TopRight:
                        rcImage.X = rc.Right - lW - 2;
                        rcImage.Y = rc.Top + 2;
                        break;
                }

                gfx.DrawImage(
                    m_bmpBackImage,
                    rcImage
                );
            }
        }

Implementation

DrawMenuItems is a core method of a menu control.

private void DrawMenuItems(
             Graphics gfx,
             Rectangle rc,
             float r
            )
        {
            Rectangle rcItem = new Rectangle();
            bool bVertical = (m_eMenuOrientation == VistaMenuOrientation.Vertical) 
				? true : false;

            if (bVertical)
            {
                rcItem.X = 5;
                rcItem.Y = 4;
                rcItem.Width = rc.Width - 10;
                rcItem.Height = m_lItemHeight;
            }
            else
            {
                rcItem.X = 5;
                rcItem.Y = 4;
                rcItem.Width = m_lItemWidth;
                rcItem.Height = rc.Height - 7;
            }
            if (m_bDrawBar){

               rcItem.X = m_lBarWidth;
               rcItem.Width -= m_lBarWidth - 5;
            }

            Rectangle rcUpRect = rcItem;
            Rectangle rcDownRect = rcItem;

            rcUpRect.Height /= 2;
            rcDownRect.Height /= 2;
            rcDownRect.Y = rcUpRect.Height + 3;

            if (items == null || items.Count == 0)
                return;

            gfx.SmoothingMode = SmoothingMode.HighQuality;
            gfx.CompositingQuality = CompositingQuality.HighQuality;
            gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            foreach (VistaMenuItem item in items) {

                #region Draw selection / checked state
                try
                {
                    item.Left = rcItem.X;
                    item.Top = rcItem.Y;
                    Rectangle rcItemInner = rcItem;
                    if (item.Checked)
                    {
                        if (item.Hovering)
                        {
                            FillMenuItem(
                                gfx,
                                rcUpRect,
                                rcDownRect,
                                item.CheckedStartColor,
                                item.CheckedEndColor,
                                item.CheckedStartColorStart,
                                item.CheckedEndColorEnd,
                                r
                            );
                            DrawItemBorder(
                                   gfx,
                                   rcItemInner,
                                   item.MouseDown,
                                   item.InnerBorder,
                                   item.OuterBorder,
                                   r
                            );
                        }
                        else
                        {
                            FillMenuItem(
                                gfx,
                                rcUpRect,
                                rcDownRect,
                                item.CheckedStartColor,
                                item.CheckedEndColor,
                                item.CheckedStartColorStart,
                                item.CheckedEndColorEnd,
                                r
                            );
                            DrawItemBorder(
                                    gfx,
                                    rcItemInner,
                                    item.MouseDown,
                                    item.InnerBorder,
                                    item.OuterBorder,
                                    r
                             );
                        }
                    }
                    else
                    {
                        if (item.Hovering)
                        {
                            if (!item.Disabled)
                            {

                                FillMenuItem(
                                    gfx,
                                    rcUpRect,
                                    rcDownRect,
                                    item.SelectionStartColor,
                                    item.SelectionEndColor,
                                    item.SelectionStartColorStart,
                                    item.SelectionEndColorEnd,
                                    r
                                );
                                DrawItemBorder(
                                    gfx,
                                    rcItemInner,
                                    item.MouseDown,
                                    item.InnerBorder,
                                    item.OuterBorder,
                                    r
                                );
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show(
                        e.ToString()
                    );
                }
                #endregion

                #region Draw icons

                if (item.Image != null)
                {
                    Rectangle rcIcon = new Rectangle();
                    rcIcon.X = rcItem.X + 2;
                    rcIcon.Y = rcItem.Bottom - item.Image.Height;
                    rcIcon.Width = item.Image.Width;
                    rcIcon.Height = item.Image.Height;

                    if (item.Disabled)
                    {
                        ControlPaint.DrawImageDisabled(
                            gfx,
                            item.Image,
                            rcIcon.X,
                            rcIcon.Y,
                            Color.Transparent);
                    }
                    else
                    {
                        gfx.DrawImage(
                            item.Image,
                            rcIcon
                        );
                    }
                }

                #endregion

                #region Draw separators
                if (m_bSeparators)
                {
                    Point pStart = new Point();
                    Point pEnd = new Point();
                    if (bVertical)
                    {
                        pStart = new Point(rcItem.Left + 3, rcItem.Bottom);
                        pEnd = new Point(rcItem.Right - 3, rcItem.Bottom);
                    }
                    else
                    {
                        pStart = new Point(rcItem.Right, rcItem.Top);
                        pEnd = new Point(rcItem.Right, rcItem.Bottom);
                    }
                    using (Pen pInner = new Pen(m_clrInnerBorder),
                                    pOuter = new Pen(m_clrOuterBorder))
                    {

                        if (!m_bFlatSeparators)
                        {
                            // don't draw separator for last item:
                            if (items.IndexOf(item) < items.Count - 1)
                            {
                                if (bVertical)
                                {

                                    gfx.DrawLine(pOuter, pStart, pEnd);
                                    pStart.Y += 1; pEnd.Y += 1;
                                    gfx.DrawLine(pInner, pStart, pEnd);
                                }
                                else
                                {
                                    gfx.DrawLine(pOuter, pStart, pEnd);
                                    pStart.X += 1; pEnd.X += 1;
                                    gfx.DrawLine(pInner, pStart, pEnd);
                                }
                            }
                        }
                        else
                        {
                            Pen pFlat = new Pen(m_clrFlatSeparators);
                            // don't draw separator for last item:
                            pStart.Y += 1; pEnd.Y += 1;
                            if (items.IndexOf(item) < items.Count - 1)
                                gfx.DrawLine(pFlat, pStart, pEnd);
                            // clean up:
                            pFlat.Dispose();
                        }

                    }
                }
                #endregion

                #region Draw item's text
                StringFormat sf = new StringFormat();
                StringFormat sfUpper = new StringFormat();
                sfUpper.Trimming = StringTrimming.EllipsisCharacter;
                sfUpper.FormatFlags = StringFormatFlags.LineLimit;
                sf.Trimming = StringTrimming.EllipsisCharacter;
                sf.FormatFlags = StringFormatFlags.LineLimit;

                Rectangle rcCaption = rcUpRect;
                Rectangle rcContent = rcDownRect;

                if (item.Image != null)
                {
                    sfUpper.Alignment = StringAlignment.Near;
                    sfUpper.LineAlignment = StringAlignment.Near;
                    sfUpper.LineAlignment = StringAlignment.Center;
                    sf.Alignment = StringAlignment.Near;

                    Rectangle rcImage = new Rectangle(
                          rcItem.X + 2,
                          rcItem.Y,
                          item.Image.Width,
                          item.Image.Height);

                    rcCaption.X = rcImage.Right + 2;
                    rcContent.X = rcImage.Right + 4;
                    rcCaption.Width -= rcImage.Width;
                    rcContent.Width -= rcImage.Width + 4;
                }
                else
                {
                    sfUpper.Alignment = StringAlignment.Center;
                    sfUpper.LineAlignment = StringAlignment.Near;
                    sfUpper.LineAlignment = StringAlignment.Center;
                    sf.Alignment = StringAlignment.Center;
                }
                // draw text for item's caption / description:
                SolidBrush sbCaption = new SolidBrush(Color.Empty);
                SolidBrush sbContent = new SolidBrush(Color.Empty);
                if (item.Checked)
                {
                    sbCaption.Color = item.CheckedCaptionColor;
                    sbContent.Color = item.CheckedContentColor;
                }
                else
                {
                    sbCaption.Color = item.CaptionColor;
                    sbContent.Color = item.ContentColor;
                }

                gfx.DrawString(item.Text, item.CaptionFont, sbCaption, 
				rcCaption, sfUpper);
                gfx.DrawString(item.Description, item.ContentFont, 
				sbContent, rcContent, sf);

                sfUpper.Dispose();
                sf.Dispose();
                sbCaption.Dispose();
                sbCaption.Dispose();
                #endregion

                #region Update positions
                if (bVertical)
                {
                    rcUpRect.Y += rcItem.Height + 2;
                    rcDownRect.Y += rcItem.Height + 2;
                    rcItem.Y += rcItem.Height + 2;
                }
                else
                {
                    rcUpRect.X += rcItem.Width + 2;
                    rcDownRect.X += rcItem.Width + 2;
                    rcItem.X += rcItem.Width + 2;

                }
                #endregion
            }
        }

It controls the rendering process of menu items, depending on item's state (disabled, hovering, checked). Also, it specifies how item caption, description, image and separators are rendered according to the orientation of a menu.

Each of the menu items is represented by VistaMenuItem class. As shown in the class diagram, there are a huge number of properties to customize an item's appearance. VistaMenuItems is a class extended from CollectionBase and it holds all menu items, and controls the addition / deletion. This way, we can easily perform hittest operations, when searching the hovering item, or detecting mouse clicks over target menu item.
Other important methods, related to menu's functionality are HitTestItem, and CalcMenuSize.

        private int HitTestItem(
            int x,
            int y
            )
        {
            int code = -1;
            VistaMenuItem item = null;

            if ((x > m_lBarWidth) && (x <= this.ClientRectangle.Width))
            {
                if ((y >= 2) && (y <= this.ClientRectangle.Height))
                {

                    for (int idx = 0; idx < items.Count; idx++)
                    {
                        item = items[idx];
                        if (y >= item.Top)
                        {
                            if (y < item.Top + m_lItemHeight)
                            {
                                code = idx;
                                break;
                            }
                        }
                    }
                }
            }
            if (code == -1)
            {
                // cursor inside side bar:
                for (int i = 0; i < items.Count; i++)
                {
                    // unhover any hovering item:
                    items[i].Hovering = false;
                    Cursor = Cursors.Default;
                    Invalidate();
                }
            }
            return code;
        }

        public void CalcMenuSize()
        {
            if (!this.DesignMode){

                int lHeight = (items.Count  ) * (m_lItemHeight + 3 ) + 1 ;
                int lWidth = (items.Count) * (m_lItemWidth + 4) + 1;
                if (m_eMenuOrientation == VistaMenuOrientation.Vertical)
                {
                    this.MaximumSize = new Size(this.Width, lHeight);
                    this.Size = new Size(this.Width, lHeight);
                }
                else
                {

                    this.MinimumSize = new Size(this.m_lItemWidth, this.m_lItemHeight);
                    this.MaximumSize = new Size(lWidth, this.m_lItemHeight + 5);
                    this.Size = new Size(lWidth, this.m_lItemHeight + 5);
                }
                Invalidate();
            }
        }
    }

The first of them is used to find an hovering item when the sidebar is rendered. It first checks if mouse is inside the menu area, excluding sidebar rectangle width. If condition evaluates to true, then we iterate through items collection, and find the correct item index. The second one dynamically adjusts menu height or width, depending on menu's orientation.

New Features

One of the new cool features in this update is the ability to show menu horizontally. This feature is controlled by MenuOrientation property. I found this feature specially interesting to use in the main application's window where you can introduce parts of your application. For example, Nero StartSmart Essentials. Note that, when menu is shown horizontally, sidebar can't be rendered.

Another useful feature implemented, is checked state. When item is checked, you can specify , item's gradient colors, and also caption / description font color. To activate this feature you should set CheckOnClick feature to true.

You can programmatically get / select menu item using SelectedItem property.

public int SelectedItem
{
     get
     {
         int idx = -1;
         for (int i = 0; i < items.Count; i++) {
               if (items[i].Checked)
                    idx = items.IndexOf(items[i]);
         }
         return idx;
      }
      set
      {
         if (value < 0 || value > items.Count)
            return;

         for (int i = 0; i < items.Count; i++){
              if (items[value].Disabled)
                  return;

              items[value].Checked = true;
         }
         Invalidate();
      }
}	

The last feature provided in this release is the possibility to disable separators rendering.

Demo Project

Click to enlarge image

Please make sure that you copy Visitor TT2 BRK font to your Fonts folder, before running the sample project.

History

  • 7th November, 2008: Initial version
  • 21th November, 2008: Repainting issue fixed
  • 25th November, 2008: Added new features
  • 28th December, 2008: Second release

License

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

Share

About the Author

Nedim Sabic
Software Developer
Spain Spain
No Biography provided

Comments and Discussions

 
QuestionMake availability of SUBMENU Pinmembershriram201216-Apr-12 4:07 
GeneralMy vote of 5 PinmemberRAJI @Codeproject28-Oct-11 20:53 
GeneralMy vote of 5 PinmemberFernandaUY20-Dec-10 4:27 
GeneralReally nice and great Pinmemberblackeyes24-Sep-10 11:48 
GeneralThanks PinmemberJohn Brown20-Jul-10 23:08 
GeneralMy vote of 5 PinmemberJohn Brown20-Jul-10 23:06 
GeneralGreat Work Pinmemberasmaqureshi821116-Jun-10 21:18 
GeneralRe: Great Work PinmemberNedim Sabic18-Jun-10 7:29 
GeneralI like it PinmemberYves12-May-10 11:21 
Generalthanks for sharing, but Pinmembersharpprogrammer23-Feb-10 1:28 
GeneralNice one, thanks for sharing, but PinmemberAtharAli20-Feb-10 6:37 
GeneralRe: Nice one, thanks for sharing, but PinmemberNedim Sabic22-Feb-10 23:51 
Generalkeyboard keys PinmemberAtharAli20-Feb-10 2:27 
GeneralTruly Briliant PinmemberAllan Gaunt13-Feb-10 11:23 
GeneralRe: Truly Briliant PinmemberNedim Sabic16-Feb-10 5:05 
GeneralVery Nice Pinmemberamir1917-Dec-09 6:09 
GeneralBravo Majstore! PinmemberAmer Gerzic7-Apr-09 6:51 
GeneralContext menu Pinmemberlimit200619-Mar-09 17:53 
GeneralScrollBar Pinmemberbuzzness00710-Feb-09 11:08 
Generaldraw icon's item position PinmemberEdward11119-Jan-09 15:26 
Questionitems at design time? PinmemberEdward11119-Jan-09 15:20 
AnswerRe: items at design time? PinmemberNedim Sabic19-Jan-09 21:22 
GeneralRe: items at design time? PinmemberEdward11120-Jan-09 6:08 
GeneralRe: items at design time? PinmemberNedim Sabic20-Jan-09 8:53 
Generalfont used PinmemberEdward11119-Jan-09 11:41 
GeneralRe: font used PinmemberNedim Sabic19-Jan-09 21:20 
GeneralOpinion sobre articulo PinmemberMember 47541155-Jan-09 17:40 
GeneralGreat! PinmemberHugo Tomas4-Jan-09 7:03 
GeneralRe: Great! PinmemberNedim Sabic4-Jan-09 8:21 
GeneralRe: Great! PinmemberPaul Selormey4-Jan-09 12:30 
GeneralRe: Great! PinmemberNedim Sabic4-Jan-09 21:09 
GeneralImpressive PinmemberVCKicks3-Jan-09 20:08 
GeneralIndex Pinmemberfrank donckers14-Nov-08 3:01 
GeneralRe: Index PinmemberNedim Sabic14-Nov-08 3:28 
Generaladd Key property PinmemberUnruled Boy13-Nov-08 19:20 
GeneralVery cool PinmemberDr.Luiji13-Nov-08 12:05 
QuestionMaximizing Container Behaviour PinmemberGustavo Valdes13-Nov-08 3:22 
AnswerRe: Maximizing Container Behaviour PinmemberNedim Sabic14-Nov-08 3:31 
QuestionMenuStrip Pinmemberfmaeseele12-Nov-08 2:24 
GeneralExcellent PinmemberQuinton Viljoen12-Nov-08 0:26 
Questiontrue launch bar??? PinmemberTanmay Broachwala10-Nov-08 18:33 
GeneralNice One... PinmemberPaul Conrad10-Nov-08 18:16 
Questiondisabled item should not be hover? PinmemberUnruled Boy10-Nov-08 15:32 
GeneralGreat work! PinmemberSupermanDT10-Nov-08 9:06 
GeneralFuturama alone is worth 5 stars PinmemberScott Bruno10-Nov-08 3:32 
QuestionLooks nice, Can you provide checked item state? Pinmembersakthi s10-Nov-08 0:09 
AnswerRe: Looks nice, Can you provide checked item state? PinmemberNedim Sabic10-Nov-08 0:35 
Generalmore separator style PinmemberUnruled Boy9-Nov-08 18:02 
GeneralRe: more separator style PinmemberNedim Sabic9-Nov-08 21:58 
GeneralGood start! PinmemberTefik Becirovic9-Nov-08 14:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 3 Jan 2009
Article Copyright 2008 by Nedim Sabic
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid