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

WinForms 的平面风格菜单栏和弹出菜单控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (44投票s)

2005 年 11 月 9 日

CPOL

6分钟阅读

viewsIcon

216117

downloadIcon

4838

一个自定义控件,用于为您的 WinForms 应用程序添加扁平风格的菜单栏、导航栏或弹出菜单

引言

本文介绍了一个扁平风格的菜单控件,可用于实现菜单栏、简单的导航栏或出现在窗体任何位置的弹出菜单。FlatMenuBar 控件支持菜单栏,而 FlatPopupMenu 控件可用于显示弹出(或上下文)菜单。我创建这些控件的目的是获得视觉上干净的菜单外观和更简单的 UI 导航模型,类似于您在某些网站上看到的菜单。我还希望支持某些效果,例如弹出菜单的渐变色背景。但我的意图并不是让此控件替代或作为 .NET 类框架中现有 Win Forms 菜单功能的替代品。例如,某些标准菜单功能(如键盘导航)目前不受支持。

菜单框架

FlatMenuBarFlatPopupMenu 是自定义控件,基于我在源文件 FlatMenu.cs 中定义的、位于 MenuControls 命名空间下的小型框架。这些菜单框架类描述如下。

FlatMenuItemList 本质上是同一菜单栏或弹出菜单上显示的一组菜单项的容器。下面显示了该类的 public 接口供参考

public class FlatMenuItemList
{
    // Properties.
    public int Count {...}
    
    // Constructor.
    public FlatMenuItemList() {...}
    
    // Manage menu items.
    public bool         Add(FlatMenuItem item) {...}
    public FlatMenuItem Add(string text,
                            EventHandler clickHandler) {...}
    public bool         AddSeparator() {...}
    public void         Clear() {...}
    public FlatMenuItem GetAt(int index) {...}
    public bool         Remove(FlatMenuItem item) {...}
    public bool         RemoveAt(int index) {...}
    
    // Output menu item Text values
    // into a single string.
    public override string ToString() {...}
}

FlatMenuItem 封装了单个菜单项的信息。使用 FlatMenuItemList 实现,它还可以包含子菜单项(以创建从该菜单项扩展出的子菜单)。下面显示了该类的 public 接口供参考

public class FlatMenuItem
{    
    // Events.
    public event System.EventHandler Click;
    
    // Properties.
    public bool              Checked {...}
    public Rectangle         ClientRectangle {...}    
    public bool              Enabled {...}  
    public bool              HasClickHandler {...}  
    public FlatMenuItemList  MenuItems {...}
    public bool              Radio {...}
    public bool              Selectable {...}   
    public FlatMenuItemStyle Style {...}
    public string            Text {...}    
   
    // Constructor.
    public FlatMenuItem() {...}
        
    // Event handling.
    public void NotifyClickEvent() {...}
    
    // Output the Text value into a string.
    public override string ToString() {...}
}

FlatMenu 是一个 abstract 类,也是 FlatMenuBarFlatPopupMenu 的基类。这是框架的关键组件。FlatMenu 声明了四个 abstract 方法,派生类必须实现这些方法,以提供菜单项矩形定位、绘制菜单、弹出菜单定位以及显示弹出菜单的特定功能。尽管 FlatMenuabstract 的,但它实际上实现了大部分菜单导航逻辑,只将绘制(视觉外观)和定位细节留给派生类提供。下面显示了该类的 public 成员和 abstract 方法供参考

public abstract class FlatMenu : System.Windows.Forms.Control
{
    // Events.
    public event CurrentMenuItemChangeEventHandler
                     CurrentMenuItemChange;

    // Menu ID property, useful for debugging
    // or identifying menu instances.
    public int MenuId {...}
    
    // Color properties for normal menu item state.
    public GradientColor BackGradientColor {...}
    public Color         BorderColor {...}
    public Color         SeparatorColor {...}
    public Color         TextColor {...}

    // Color properties for menu item hover state.
    public GradientColor HoverBackGradientColor {...}
    public Color         HoverBorderColor {...}
    public Color         HoverTextColor {...}
    
    // Disabled menu item text color.
    public Color DisabledTextColor {...}
    
    // Text font for menu item hover state.
    public Font HoverFont {...}
    
    // Options for border and background drawing.
    public bool EnableBorderDrawing {...}
    public bool EnableHoverBorderDrawing {...}
    public bool EnableHoverBackDrawing {...}
    
    // Offset of first menu item text rectangle
    // from top-left corner of control.
    public int LeftMargin {...}
    public int TopMargin {...}
    
    // Horizontal or vertical spacing between
    // menu items.
    public int MenuItemSpacing {...}
    
    // Timer interval for auto-closing menu.
    public int MenuTimerInterval {...}
    
    // Is this a popup-style menu?
    public bool IsPopupMenu {...}
    
    // Access the popup menu.
    public FlatMenu Popup {...}
    
    // Parent menu and parent menu item.
    public FlatMenu     ParentMenu {...}
    public FlatMenuItem ParentMenuItem {...}
    
    // List of child menu items.
    public FlatMenuItemList MenuItems {...}
    
    // Constructor.
    public FlatMenu() {...}
    
    // Output the menu ID into a string.
    public override string ToString() {...}
    
    // Abstract methods.
    protected abstract void DrawMenu(Graphics g);
    protected abstract void 
        RepositionMenuItems(Graphics g);
    protected abstract void CreatePopupMenu();
    protected abstract void
        GetPopupMenuScreenLocation(ref Point ptScreen);
}

使用 FlatMenuBar

FlatMenuBarFlatPopupMenu 控件类定义在源文件 FlatMenuBar.cs 中。如前所述,它们都派生自 FlatMenu。一个有趣的观察是,FlatMenuBar 本身使用 FlatPopupMenu 来实现级联子菜单。

一旦源代码被编译成 DLL 或 EXE,您就可以在设计器中打开您的窗体,然后选择“工具”|“添加/删除工具箱项”,以便将 FlatMenuBar 控件添加到您的 VS 工具箱。添加后,您可以将 FlatMenuBar 实例拖到您的窗体上 - 它将显示为一个简单的海军蓝色矩形,带有灰色文本,您可以根据需要重新定位和调整大小。Visual Studio 会为控件变量分配一个默认名称,例如 flatMenuBar1

一旦创建了菜单栏实例,我们就可以开始添加菜单项。我通常会编写一个单独的 private 方法来完成所有设置和初始化工作。然后,我从窗体的构造函数中调用此方法。例如,要创建一个顶级的 File 菜单项,我们可以编写如下所示的 LoadMenuBar1() 方法

public class MainForm : System.Windows.Forms.Form
{
    ...

    public MainForm()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        
        // Populate menu bar 1.
        LoadMenuBar1();
    }
    
    private void LoadMenuBar1()
    {
        // File menu item.
        FlatMenuItem fileItem = new FlatMenuItem();
        fileItem.Text = "File";
        this.flatMenuBar1.MenuItems.Add(fileItem);
    }
}

现在,假设我们想创建一个 File 菜单,其中包含 NewExit 等菜单项,以及它们之间的分隔符项。以下代码演示了如何做到这一点

private void LoadMenuBar1()
{
    ...
    
    // File | New menu item.
    FlatMenuItem newItem = new FlatMenuItem();
    newItem.Text = "New";
    fileItem.MenuItems.Add(newItem);

    // Add separator item after New.
    fileItem.MenuItems.AddSeparator();

    // File | Exit menu item.
    FlatMenuItem exitItem = new FlatMenuItem();
    exitItem.Text = "Exit";
    fileItem.MenuItems.Add(exitItem);    
}

接下来,如果我们想让 New 菜单项扩展到更进一步的子菜单,其中包含 Window 菜单项,我们可以添加以下代码

private void LoadMenuBar1()
{
    ...
    
    // File | New | Window menu item.
    FlatMenuItem windowItem = new FlatMenuItem();
    windowItem.Text = "Window";
    newItem.MenuItems.Add(windowItem);
}

File 菜单打开时,生成的菜单栏应该如下所示

事件处理

除了显示菜单项之外,我们还想添加一个回调函数,当用户选择菜单项时调用它。为此,我们可以为菜单项的 Click 事件添加一个事件处理程序

public class MainForm : System.Windows.Forms.Form
{
    ...

    private void LoadMenuBar1()
    {
        ...
        windowItem.Text = "Window";
        windowItem.Click +=
            new System.EventHandler(windowItem_Click);
        ...
    }

    private void windowItem_Click(object obj,
                                  System.EventArgs e)
    {
        MessageBox.Show("New Window Clicked");
    }
}

请注意,菜单项的 Click 事件处理程序只有在菜单项没有自己的子项且菜单项已启用时才有效。例如,如果您使用如下所示的代码禁用菜单项,则 Click 处理程序将被忽略(实际上,该菜单项甚至无法选中)

private void LoadMenuBar1()
{
    ...
    windowItem.Enabled = false;
    ...
}

客户端代码还可以从菜单本身(FlatMenuBarFlatPopupMenu 实例)接收另一个事件。这就是 CurrentMenuItemChange 事件,当用户浏览菜单项时,菜单会触发此事件。例如,此事件可用于显示当前突出显示的菜单项的描述

public class MainForm : System.Windows.Forms.Form
{
    ...

    private void LoadMenuBar1()
    {
        ...
    
        // Add an event handler.
        this.flatMenuBar1.CurrentMenuItemChange +=
            new CurrentMenuItemChangeEventHandler(
                  flatMenuBar1_CurrentMenuItemChange);
    }

    private void flatMenuBar1_CurrentMenuItemChange(
                     object obj,
                     CurrentMenuItemChangeEventArgs e)
    {      
        // Display the name of the currently
        // highlighted menu item.
        FlatMenuItem item = e.CurrentMenuItem;
        if ( item == null )
            this.currItemLabel.Text = "None";
        else
            this.currItemLabel.Text = item.Text;
    }
}

使用 FlatPopupMenu

FlatPopupMenu 实例可以像 FlatMenuBar 一样加载菜单项。唯一的区别是,没有必要先将控件实例拖到窗体上。可以按需创建实例,初始化,然后根据需要显示(例如,在上下文菜单右键单击时)。可以通过调用其 TrackPopupMenu() 方法来显示 FlatPopupMenu 实例,该方法有两个重载版本

public class FlatPopupMenu : MenuControls.FlatMenu
{
    ...
        
    public void TrackPopupMenu(Control parent,
                    FlatMenuAlignment alignX,
                    FlatMenuAlignment alignY,
                    int x, int y) {...}
        
    public void TrackPopupMenu(Control parent, 
                               int x, int y) {...}
}

以下是一个在窗体上鼠标右键单击时显示弹出菜单的示例代码

public class MainForm : System.Windows.Forms.Form
{
    ...
    
    private FlatPopupMenu popupMenu = null;
    
    protected override void Dispose( bool disposing )
    {
        if ( disposing )
        {
            // Dispose managed resources.
            ...           
            if ( this.popupMenu != null )
            {
                this.popupMenu.Dispose();
                this.popupMenu = null;
            }
        }
        base.Dispose( disposing );
    }

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

        // Only process mouse right-clicks.
        if ( e.Button != MouseButtons.Right )
            return;

        ShowPopupMenu(e.X, e.Y);
    }
    
    private void ShowPopupMenu(int x, int y)
    {
        if ( this.popupMenu == null )
        {
            // Create the popup menu.
            this.popupMenu = new FlatPopupMenu();
                
            // Set color properties.
            this.popupMenu.BackColor = Color.Red;
                
            // Add menu items.
            FlatMenuItem popupItem1 = new FlatMenuItem();
            popupItem1.Text = "Popup Item 1";
            FlatMenuItem popupItem2 = new FlatMenuItem();
            popupItem2.Text = "Popup Item 2";
            FlatMenuItem popupItem3 = new FlatMenuItem();
            popupItem3.Text = "Popup Item 3";                
            this.popupMenu.MenuItems.Add(popupItem1);
            this.popupMenu.MenuItems.Add(popupItem2);
            this.popupMenu.MenuItems.Add(popupItem3);
        }
            
        // Display the popup menu at the given
        // mouse location.
        this.popupMenu.TrackPopupMenu(this, x, y);
    }    
}

TestFlatMenu 应用程序

演示项目 (TestFlatMenu) 是一个简单的 WinForms 应用程序,它显示了五种不同类型的菜单栏和一个弹出菜单(上下文菜单)。我决定不让 Click 事件处理程序仅显示消息框,而是做一些更有趣的事情,让 Click 事件触发在嵌入式 Microsoft Web Browser 控件中打开网页。

注意:要将此控件添加到您自己的 VS 工具箱,只需使用“工具”|“添加/删除工具箱项”,选择“COM 组件”选项卡,然后查找 Microsoft Web Browser 控件。下面是应用程序的快照,显示了一个在窗体上鼠标右键单击时打开的 FlatPopupMenu 实例

摘要

我认为本文提出的菜单控件更适合创建具有 Web 风格 UI 行为的导航栏。这是我的初衷——我并没有真正打算将该控件用作 WinForms 应用程序的主菜单。此外,该控件实现了我自行选择的默认属性的特定视觉样式。其他用户可能有不同的需求,因此,如果您需要不同的菜单外观,一个选项是从 FlatMenu 派生您自己的类。FlatMenuBarFlatPopupMenu 都可以用作如何实现所需的四个 abstract 方法的示例。例如,将来,我可能会考虑使用此框架实现一个垂直导航栏,并提供文本左对齐或右对齐的选项等。

历史

  • 2005 年 11 月 9 日
    • 初始修订版
  • 2005 年 11 月 10 日
    • 添加了第二个仅包含打包到 MenuControls 类库 (DLL) 中的源代码(及二进制文件)的下载链接
    • MenuId 属性更改为不可枚举。还添加了一些最小的设计模式专用绘制
  • 2005 年 11 月 12 日
    • 添加了 InvalidateMenu() 以在某些属性更改时刷新控件(主要用于设计模式)
  • 2005 年 11 月 14 日
    • 默认将 TabStop 属性设置为 false,并使更多属性不可枚举。
  • 2006 年 1 月 30 日
    • 添加了新属性:EnableBorderDrawingEnableHoverBorderDrawingEnableHoverBackDrawingHoverFont
    • BackGradientColorHoverBackGradientColor 替换了 SecondaryBackColorHoverBackColor 属性。允许菜单栏和悬停矩形使用渐变背景绘制
    • 添加了 Popup 属性以允许访问子弹出菜单。对于为子弹出菜单应用不同的外观设置很有用
    • 增加了对选中/单选菜单项的支持
    • 允许演示应用程序中的主窗体调整得更大
    • 为 VS 2003 和 VS 2005 添加了单独的下载。每个 zip 文件包含完整的源代码,并包含 MenuControls 类库 (DLL) 的发布版本。
© . All rights reserved.