从 MainMenu 和 ToolBar 升级到 MenuStrip 和 ToolStrip






4.84/5 (36投票s)
2006年2月4日
8分钟阅读

284790

2956
升级菜单和工具栏到 .NET 2.0 的分步指南,以及用于改进用户界面的代码。

引言
在 .NET Framework 的 2.0 版本中,对菜单和工具栏的支持得到了升级。不幸的是,每个新类的结构都发生了巨大变化,转换并非易事。此外,用户界面也表现出一些意想不到的行为。本文提供了一些关于进行转换的技巧。它还介绍了 `ToolStrip` 和 `MenuStrip` 类的简单扩展,这些扩展允许自定义鼠标悬停高亮显示并实现“点击穿透”以改进用户界面。
背景
和我一样,您可能有一些为 .NET Framework 1.x 版本编写的旧代码,您希望将其升级到最新版本中提供的更精美的菜单和工具栏。`MainMenu` 和 `ContextMenu` 类已被全新的类 `MenuStrip` 和 `ContextMenuStrip` 取代。同样,`ToolBar` 类已被 `ToolStrip` 取代。新类具有更现代的外观,并且可以以新的方式进行扩展。例如,您可以轻松地将 `ComboBoxes` 放入菜单中,或将进度条放入 `ToolStrip` 或 `StatusStrip` 中。由于新类的结构差异很大,升级旧控件并非完全直接。我在网上徒劳地寻找一个简单的过程。最后,我决定记录下我所做的事情,并与 Code Project 社区分享。
在经历过转换的麻烦之后,您可能会发现新控件的行为方式出乎您的意料,这可能不合您的口味。有两个问题。首先,与大多数 Windows 应用程序不同,当父窗体没有焦点时,按钮和菜单不响应单击。您必须单击两次控件才能使其响应,一次激活窗体,第二次激活控件。这对于用户界面来说是一个非常好的模型,也是 Office 2003 和 Visual Studio 2005 的工作方式。但是,如果用户可以选择此模型或更标准的激活控件的单次单击方法,那会更好。此外,新控件有一个让我非常困惑的“特性”(我尽量说得委婉)。与 Office 2003 不同,`ToolStrip` 或 `MenuStrip` 在鼠标悬停在非活动窗体上时仍会显示高亮。对我来说,这意味着它们已准备好响应,但它们仍然需要第二次单击。
我借用了 Rick Brewster 的代码,扩展了 `ToolStrip` 和 `MenuStrip` 类来实现一个允许单击一次即可激活控件的特性。对于那些偏爱 Office 2003 用户界面的用户,我还实现了一个“抑制高亮显示”功能,以便仅在父窗体(或其子控件之一)处于活动状态时才进行鼠标悬停高亮显示。我包含了代码和一个示例应用程序来演示这些新类的行为。
通过搜索和替换进行升级
如果您正在编写新应用程序,则应该没有问题,可以使用 Visual Studio 设计器创建新的 `MenuStrip` 和 `ToolStrip`。但是,如果您正在升级代码,则在设计器中重新创建所有旧菜单和工具栏是一项艰巨的任务。如果您使用搜索和替换,则不清楚如何进行,并且很容易出错。我的目标是通过提供主要更改的分步指南来节省您一些时间。它不会涵盖所有可能性,因此在初始编译后您可能需要更正一些内容。
首先,我强烈建议您在开始编辑之前保存整个项目的备份。手动更改设计器生成的代码总是很危险的。如果您犯了错误,或者我的提示将您引入歧途,您可能会最终得到设计器无法解释的代码,从而阻止您修改窗体,直到找到错误为止。其次,新类结构很复杂,因此我的建议可能仅能让您入门。我的建议是先尝试此处列出的简单更改,然后如果编译器抱怨,再解决任何剩余问题。
我将假定您正在使用 Visual Studio 编辑代码。打开代码视图并关闭要升级的窗体的设计视图。请按顺序遵循这些提示,使用加粗高亮显示的文本作为搜索和替换字符串。选择“区分大小写”、“搜索隐藏文本”和“搜索当前文档”选项。一次执行一个替换,并仔细检查每个替换是否在上下文中都有意义。
升级到 MenuStrip 和 ContextMenuStrip 的技巧
- 将
MainMenu
替换为MenuStrip
- 将
ContextMenu
替换为ContextMenuStrip
- 如果
MainMenu
构造函数或ContextMenu
构造函数带有参数,请将其移除 - 将
Menu.GetMainMenu()
替换为ToolStripItem.GetCurrentParent()
,返回值是ToolStrip
,您可能需要显式将其转换为MenuStrip
- 将
MenuStrip.MenuItems
和ContextMenuStrip.MenuItems
替换为MenuStrip.Items
或ContextMenuStrip.Items
,但请确保您只对此类项目执行此操作,而不是对MenuItems
执行。 - 将
MenuItem.MenuItems
替换为ToolStripMenuItem.DropDownItems
- 将
this.Menu
替换为this.MainMenuStrip
- 将
MenuItem
替换为ToolStripMenuItem
(以及将MenuItems
替换为ToolStripMenuItems
) - 将所有
ToolStripMenuItem.Index
属性更改为ToolStripMenuItem.MergeIndex
- 将
ToolStripMenuItem.Popup
事件替换为ToolStripDropDownItem.DropDownOPENING
事件,将ContextMenuStrip.Popop
事件替换为ContextMenuItem.Opened
事件。 - 删除设置
MenuItem.Shortcut
属性的行,稍后在设计器中重新设置它们,或者编辑该行以使用Form.Keys
枚举设置ToolStripMenuItem.ShortcutKeys
属性。 - 将
ToolStripMenuItem.ShowShortcut
替换为ToolStripMenuItem.ShowShortcutKeys
- 删除设置
ToolStripMenuItem.MergeOrder
属性的行 - 删除设置
ToolStripMenuItem.RadioCheck
属性的行 - 删除设置
ToolStripMenuItem.MdiList
属性的行 - 如果
mnMdiList
是您希望显示 MDI 子窗体列表的MenuItem
的名称,请设置MainMenuStrip.MdiWindowListItem = this.mnMdiList
。 - 在
InitializeComponent()
方法中找到包含this.Controls.Add
的行。如果您的MenuStrip
名为mainMenu
,请将以下行添加到该方法:this.Controls.Add(this.mainMenu);
。 - 现在编译您的代码。您可能会发现错误。如果是这样,请查阅类参考来解决任何剩余问题。
- 在设计器中查看您的窗体。如果一切顺利,您将看到您的新菜单。重新调整布局以适应新菜单的不同大小。
- 设置在步骤 11 中未编辑的任何快捷键。
- 所有分隔符将显示为包含单个连字符的菜单项。在设计器中右键单击这些项,选择“转换为”,然后替换为实际的分隔符。
- 编译您的应用程序,看看它是否可以运行!
升级到 ToolStrip 的技巧
- 将
ToolBar
替换为ToolStrip
- 将
ToolBarButton
替换为ToolStripButton
- 将
ToolBarSeparator
替换为ToolStripSeparator
- 删除设置 ToolStrip.
ButtonSize
、ToolStrip.
DropDownArrows
或ToolStrip.ShowToolTips
的行,因为ToolStrip
没有这些属性。 - 将
ToolStrip.ButtonClick
事件处理程序替换为单独的ToolStripButtonClick
事件处理程序。 - 删除设置
ToolStripButton.Style
的行。 - 将
ToolStripButton.Pushed
替换为ToolStripButton.Checked
- 将
ToolStrip.Buttons.AddRange()
替换为ToolStrip.Items.AddRange()
- 使用设计器替换所有按钮的图像。
ImageList
属性仍然有效,但它已被弃用,不能在设计器中使用。 - 祈祷好运,编译并运行。
扩展 ToolStrip 和 MenuStrip 类
如上所述,我想更改这些控件的行为,以创建更直观的用户界面。我在 MSDN 上的 Rick Brewster 的博客 中找到了大部分解决方案。Rick 引入了一个 `ClickThrough` 属性,并重写了 `WndProc()` 方法来监听 `WM_MOUSEACTIVATE` 消息,并在 `ClickThrough` 为 true 时将返回结果从“Activate and Eat
”更改为“Activate
”。在一个 MSDN 论坛中,我找到了 JasonD 的建议,即在不发生点击穿透时抑制不需要的高亮显示。他建议重写 `WndProc()` 来拦截 `WM_MOUSEMOVE` 消息,除非父窗体或其子控件之一处于焦点状态,否则将其丢弃。
我结合了这两个想法来创建 ToolStripEx
和 MenuStripEx
类。示例项目包含了代码和使用示例。这两个类都包含两个可以在代码中操作或在 Visual Studio 设计器中设置的新属性。它们是:
// If true, activate control on a single click,
// even if the control's form does not have focus
public bool ClickThrough=false;
// If true, do not show mouseover highlighting
// unless the control's form has focus
public bool SuppressHighlighting=false;
有关详细信息,请参阅示例项目,并运行示例应用程序来观察效果。运行它时,请尝试所有四种变化,并观察菜单和工具栏在主窗体有焦点和在较小窗体有焦点时的响应情况。ToolStripEx
和 MenuStripEx
类中的代码是相同的,它只是定义了上述两个属性,然后实现了这个简单的 `WndProc()` 方法。
protected override void WndProc(ref Message m)
{
// If we don't want highlighting, throw away mousemove commands
// when the parent form or one of its children does not have the focus
if(m.Msg == WinConst.WM_MOUSEMOVE && this.suppressHighlighting &&
!this.TopLevelControl.ContainsFocus)
return;
else
base.WndProc(ref m);
// If we want ClickThrough, replace "Activate and Eat" with
// "Activate" on WM_MOUSEACTIVATE messages
if(m.Msg == WinConst.WM_MOUSEACTIVATE && this.clickThrough &&
m.Result == (IntPtr)WinConst.MA_ACTIVATEANDEAT)
m.Result = (IntPtr)WinConst.MA_ACTIVATE;
}
构建示例应用程序后,您可以轻松地将扩展类包含到您自己的项目中,像任何内置类一样将它们拖放到窗体上,并在设计器中对其进行编辑。
最终想法
非常感谢 Rick Brewster 和 JasonD 提供代码和建议,以实现所需的用户界面行为。