WinForms 的平面风格菜单栏和弹出菜单控件
一个自定义控件,用于为您的 WinForms 应用程序添加扁平风格的菜单栏、导航栏或弹出菜单
引言
本文介绍了一个扁平风格的菜单控件,可用于实现菜单栏、简单的导航栏或出现在窗体任何位置的弹出菜单。FlatMenuBar
控件支持菜单栏,而 FlatPopupMenu
控件可用于显示弹出(或上下文)菜单。我创建这些控件的目的是获得视觉上干净的菜单外观和更简单的 UI 导航模型,类似于您在某些网站上看到的菜单。我还希望支持某些效果,例如弹出菜单的渐变色背景。但我的意图并不是让此控件替代或作为 .NET 类框架中现有 Win Forms 菜单功能的替代品。例如,某些标准菜单功能(如键盘导航)目前不受支持。
菜单框架
FlatMenuBar
和 FlatPopupMenu
是自定义控件,基于我在源文件 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
类,也是 FlatMenuBar
和 FlatPopupMenu
的基类。这是框架的关键组件。FlatMenu
声明了四个 abstract
方法,派生类必须实现这些方法,以提供菜单项矩形定位、绘制菜单、弹出菜单定位以及显示弹出菜单的特定功能。尽管 FlatMenu
是 abstract
的,但它实际上实现了大部分菜单导航逻辑,只将绘制(视觉外观)和定位细节留给派生类提供。下面显示了该类的 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
FlatMenuBar
和 FlatPopupMenu
控件类定义在源文件 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
菜单,其中包含 New
和 Exit
等菜单项,以及它们之间的分隔符项。以下代码演示了如何做到这一点
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;
...
}
客户端代码还可以从菜单本身(FlatMenuBar
或 FlatPopupMenu
实例)接收另一个事件。这就是 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
派生您自己的类。FlatMenuBar
和 FlatPopupMenu
都可以用作如何实现所需的四个 abstract
方法的示例。例如,将来,我可能会考虑使用此框架实现一个垂直导航栏,并提供文本左对齐或右对齐的选项等。
历史
- 2005 年 11 月 9 日
- 初始修订版
- 2005 年 11 月 10 日
- 添加了第二个仅包含打包到
MenuControls
类库 (DLL) 中的源代码(及二进制文件)的下载链接 - 将
MenuId
属性更改为不可枚举。还添加了一些最小的设计模式专用绘制
- 添加了第二个仅包含打包到
- 2005 年 11 月 12 日
- 添加了
InvalidateMenu()
以在某些属性更改时刷新控件(主要用于设计模式)
- 添加了
- 2005 年 11 月 14 日
- 默认将
TabStop
属性设置为false
,并使更多属性不可枚举。
- 默认将
- 2006 年 1 月 30 日
- 添加了新属性:
EnableBorderDrawing
、EnableHoverBorderDrawing
、EnableHoverBackDrawing
和HoverFont
- 用
BackGradientColor
和HoverBackGradientColor
替换了SecondaryBackColor
和HoverBackColor
属性。允许菜单栏和悬停矩形使用渐变背景绘制 - 添加了
Popup
属性以允许访问子弹出菜单。对于为子弹出菜单应用不同的外观设置很有用 - 增加了对选中/单选菜单项的支持
- 允许演示应用程序中的主窗体调整得更大
- 为 VS 2003 和 VS 2005 添加了单独的下载。每个 zip 文件包含完整的源代码,并包含
MenuControls
类库 (DLL) 的发布版本。
- 添加了新属性: