Click here to Skip to main content
Email Password   helpLost your password?
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:

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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalthanks for sharing, but
sharpprogrammer
2:28 23 Feb '10  
hi
I tried to override OnKeyDown even but i cannot make it. Can you please help me with come.
thanks a lot
GeneralNice one, thanks for sharing, but
AtharAli
7:37 20 Feb '10  
key board buttons are not working, how to enable keyboard buttons in this menu control.
GeneralRe: Nice one, thanks for sharing, but
Nedim Sabic
0:51 23 Feb '10  
Override OnKeyDown event, and write the corresponding drawing routine to make the item hoverable.
Generalkeyboard keys
AtharAli
3:27 20 Feb '10  
hi, Keyboard down,up,right and left keys are not working.Please help
GeneralTruly Briliant
Allan Gaunt
12:23 13 Feb '10  
Just want to say thanks for this, it is truly amazing. Very easy to use.
GeneralRe: Truly Briliant
Nedim Sabic
6:05 16 Feb '10  
Thanks Smile
GeneralVery Nice
amir19
7:09 17 Dec '09  
Hello
It's very nice. I needed it so much. Thank you.
Do you know how i can dock my app to desktop like Vista Sidebar or Virtual CD sidebar?
GeneralBravo Majstore!
Amer Gerzic
7:51 7 Apr '09  
Cool

Have a good one!
Amer Gerzic
www.amergerzic.com

GeneralContext menu
limit2006
18:53 19 Mar '09  
Can I used it like the ContextMenu?
GeneralScrollBar
buzzness007
12:08 10 Feb '09  
Hi there

Could you add a scroll bar function to this object, this will help, when you have more items that can fit on the screen.

Regards

Mr B
Generaldraw icon's item position
Edward111
16:26 19 Jan '09  
I wonder if there any posibility to add on next release, a property to handle icon item's position, I mean, you can choose where icon is placed, at right, left, center, top, bottom, middle.

thanks again for share this component, you have my 5
Generalitems at design time?
Edward111
16:20 19 Jan '09  
i use vs2008 and at design time I doesn't see any items, just plain color panel, does this control works at design time?, if so, do you know what is my problem about it ?

thanks in advance for your help,
GeneralRe: items at design time?
Nedim Sabic
22:22 19 Jan '09  
The answer is no, I didnt implement design-time support for a control Smile .Anyways, its very easy to add
items. Read my article.
GeneralRe: items at design time?
Edward111
7:08 20 Jan '09  
thanks for your quick answer,

but what about WYSIWYG, its would by nice to see items at design time, how its look mixed colors, separator, space between items, gradients, etc. right now I need to make changes and run it in order to see those properties working.

however, its a great control, thanks again.
GeneralRe: items at design time?
Nedim Sabic
9:53 20 Jan '09  
Thanks.

I'm too busy now, + i'm working
on a new project. Maybe in a near future
I could implement it =)
Generalfont used
Edward111
12:41 19 Jan '09  
thanks for this great menu,
my Q is: I cannot see visitor 2 font in the demo, I only see tahoma font

where do i need to put visitor2.tff in order to link to the control and shows in the demo?

thanks again,
GeneralRe: font used
Nedim Sabic
22:20 19 Jan '09  
You have to drag & drop to your Windows Fonts directory.
GeneralOpinion sobre articulo
Member 4754115
18:40 5 Jan '09  
El articulo esta super bueno, creo que ayudara enormemenete al desarrollar aplicaciones

Luis Castillo
www.jugandocon.net

GeneralGreat!
Hugo Tomas
8:03 4 Jan '09  
How about using the krypton.Toolkit(free), to rendering the design?

http://www.componentfactory.com/free-windows-forms-controls.php[^]

Hugo Tomas

GeneralRe: Great!
Nedim Sabic
9:21 4 Jan '09  
Im actually working on new project, library to completly
change look & feel of windows forms.
hope it will be finish soon, so i can post
it here =)
GeneralRe: Great!
Paul Selormey
13:30 4 Jan '09  
Any plan to add scrolling to this nice control?

Best regards,
Paul.

Jesus Christ is LOVE! Please tell somebody.

GeneralRe: Great!
Nedim Sabic
22:09 4 Jan '09  
Maybe in some future releases =)
Regards
GeneralImpressive
VCKicks
21:08 3 Jan '09  
I love a good user control, props.

One suggestion, perhaps fire the click event when the mouse click is up instead of down. Otherwise is seems to click on the menu item almost too fast.

Visual C# Kicks - Free C#.NET articles, resources, and downloads at
http://www.vcskicks.com

GeneralIndex
frank donckers
4:01 14 Nov '08  
Great work.

I was looking for a way to get the index of a selected item, but could not find it.
This could be handy sometimes.

Also, i have been working on your project to convert it to VB.net, some problems came along, but its coming on great.
I'll let you know whem i'm finnished.

Wink
GeneralRe: Index
Nedim Sabic
4:28 14 Nov '08  
Oww that's great =).

To get the index of a selected item, you could
add a new member (Index), and when raising AlienMenuItemClick
event pass it to AlienMenuItemClickArgs constructor.


Last Updated 3 Jan 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010