65.9K
CodeProject 正在变化。 阅读更多。
Home

酷炫的 Vista 风格菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (76投票s)

2008年11月9日

CPOL

3分钟阅读

viewsIcon

164968

downloadIcon

9429

在您的 .NET 应用程序中使用酷炫的 Vista 风格菜单

Vista menu control

引言

这是此控件的第二个版本,我之前的文章有点粗糙,所以我决定也更新一下。我基于 Vista 开始菜单开发了这个控件,但正如你在上面的图片中看到的,它可以很容易地定制以支持其他样式、主题等。更多详细信息请参见演示项目(mac 风格菜单,在运行时应用主题等)。

Using the Code

使用这个控件非常简单。只需将其添加到您的工具箱,然后拖放到您的窗体上。

接下来,填充菜单控件,添加菜单项。有几个重载的方法可以用来实现这个目的。

  • 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);

所以,如果您想要丰富的菜单项,您应该使用第三种方法。这是一个简单的代码来添加一些项目

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

结构

下图显示了菜单控件的不同元素。菜单项是丰富呈现的,这意味着它们具有图像表示、标识它们的标题和简短描述。每个菜单项都由分隔符分隔。它们可以是平面的、3D 的,并且在此控件的发布版本中,您还可以指定根本不呈现它们。此功能将在稍后介绍。使用菜单侧边栏,您可以使用不同的名称对不同的菜单进行分组,并且还可以提供一个图标,该图标将在侧边栏标题旁边呈现。

菜单面板使用渐变颜色渲染,如图所示。
该控件实现了许多与菜单外观相关的属性,例如起始和结束渐变颜色、内部/外部边框颜色、侧边栏渐变颜色。

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

Vista 菜单控件也能够渲染背景图像。您可以设置图像对齐方式,由ContentAlignment枚举定义,然后调用DrawBackImage来绘制图像。

 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
                );
            }
        }

实现

DrawMenuItems是菜单控件的核心方法。

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
            }
        }

它控制菜单项的渲染过程,具体取决于项目的状态(禁用、悬停、选中)。此外,它还指定如何根据菜单的方向渲染项目标题、描述、图像和分隔符。

每个菜单项都由VistaMenuItem类表示。如图类图所示,有大量的属性可以自定义项目的外观。VistaMenuItems是一个从CollectionBase扩展的类,它保存所有菜单项,并控制添加/删除。这样,我们可以轻松地执行命中测试操作,以搜索悬停的项目或检测目标菜单项上的鼠标点击。
与菜单功能相关的其他重要方法是HitTestItemCalcMenuSize

        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();
            }
        }
    }

当渲染侧边栏时,第一个方法用于查找悬停的项目。它首先检查鼠标是否在菜单区域内,不包括侧边栏矩形的宽度。如果条件评估为true,那么我们遍历项目集合,并找到正确的项目索引。第二个方法动态调整菜单的高度或宽度,具体取决于菜单的方向。

新功能

此更新中新的酷炫功能之一是能够水平显示菜单。此功能由MenuOrientation属性控制。我发现此功能特别有趣,可以在主应用程序窗口中使用,您可以在其中引入应用程序的各个部分。例如,Nero StartSmart Essentials。请注意,当菜单水平显示时,无法渲染侧边栏。

实现的另一个有用的功能是选中状态。当项目被选中时,您可以指定项目的渐变颜色,以及标题/描述字体颜色。要激活此功能,您应该将CheckOnClick功能设置为true

您可以使用SelectedItem属性以编程方式获取/选择菜单项。

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();
      }
}	

此版本中提供的最后一个功能是禁用分隔符渲染的可能性。

演示项目

Click to enlarge image

请确保在运行示例项目之前,将 Visitor TT2 BRK 字体复制到您的字体文件夹。

历史

  • 2008年11月7日:初始版本
  • 2008年11月21日:修复了重绘问题
  • 2008年11月25日:添加了新功能
  • 2008年12月28日:第二个版本
© . All rights reserved.