使用MDICommandSupport类库处理菜单和工具栏






4.71/5 (16投票s)
简化了MDI父窗体和其他窗体中ToolStrip类型菜单和工具栏的操作;同时也适用于ToolStrip类型上下文菜单。还处理菜单和工具栏项的帮助请求。
最近更新-- 2024年9月9日 晚上9:50 (EST)
引言
每当我创建一个大型应用程序时——尤其是MDI应用程序——我经常会有一些命令(程序动作),用户可以在多个地方调用它们——即,从菜单和工具栏。这可能意味着MDI模块中有两个执行相同功能的子程序——以及需要为每个ToolStripItem
(菜单和工具栏项)设置相似的属性——例如,当某个命令需要启用/禁用或显示/隐藏时。此外,每个命令执行的代码,虽然整体不同,但常常包含某些通用指令——比如,在子程序的开头和结尾。最后,我经常想检测何时ToolStripItem
被选中(高亮)或取消选中(取消高亮)——例如,为了在状态栏标签中显示文本;不幸的是,菜单项在选中/取消选中时不会引发特定的事件。
下面的类库会扫描窗体(任何窗体,不只是MDI窗体)的控件集合,查找MenuStrip
s、ToolStrip
s以及包含ToolStripItem
s的ToolStripItem
容器,并通过命令名称(由Tag
属性的ToString
方法指定)将这些项分组到一个“命令字典”中,该字典存在于MDICommandInfo
类的实例中——这允许一次性指定状态栏文本并获取/设置与给定命令关联的所有ToolStripItem
的属性。
MDICommandInfo
实例由另一个类MDICommandHandler
管理——它会拦截所需的鼠标/键盘事件,以便检测命令何时被调用(点击)、选中和取消选中——并将这些情况委托给三个事件:CommandClicked
、CommandSelected
和CommandDeselected
。命令名称和相关状态文本的列表由用户通过ResourceManager
、Dictionary
或手动提供。
它也可以用于ContextMenuStrip
。
(新增!) 它现在还通过事件CommandHelpRequested
处理选中参与的菜单/工具栏项时的帮助请求。
(新增!) 演示程序在窗体的状态栏以及Debug
窗口中显示帮助请求的通知。
历史记录
从最新到最旧
- 截至2024年9月9日晚上9:50 EST,演示程序会在窗体的状态栏以及
Debug
窗口(现在也只用于记录菜单/工具栏项的选择和取消选择)中显示帮助请求时的通知。 - 截至2024年9月9日凌晨1:40 EST,提供了一个新事件
CommandHelpRequested
,以使MDICommandHandler
能够处理任何参与的菜单或工具栏项的应用程序帮助请求。此外,“第一个命令”的快捷键现在是Shift + F1,以便使用标准的帮助键(F1或fn + F1)来触发帮助请求。 - (BUG修复!) 截至2023年7月27日下午2:40 EST,我修复了一个bug,以防止
CommandClicked
事件的重入问题。 - (BUG修复!) 截至2023年7月18日下午4:35 EST,我修改了
MDICommandInfo
类,使其接受一个额外的参数——其父MDICommandHander
类的实例——这样,当前者的一个实例的StatusLabelText
属性被更改时,如果前者的命令名称与后者当前菜单/工具栏项的命令名称匹配,则后者(父类)中的标签会被更新。这不应该破坏您当前的代码,前提是您没有在主机代码中实例化MDICommandInfo
(而不是MDICommandHandler
)。(如果已经实例化,请在每个“New
”构造函数语句中插入父类的实例。)此修复确保了例如使用MDICommandHandler
的CommandInfo
(默认)属性所做的对StatusLabelText
的更改会立即反映在UI中。 - 截至2023年7月18日,
CommandStatusText
属性现在是可读写的,而不是只读的。设置此属性时,新的标签字符串会放入CommandDictionary
中,如果指定的命令是当前选中的项,则会放入相应的工具栏标签中。 - 截至2023年7月17日,在任何主机程序事件代码的
CommandClicked
事件运行时,CommandSelected
和CommandDeselected
事件都会被抑制——并且即使菜单项是通过快捷键调用的,它们也保证分别在CommandClicked
之前和之后触发——以确保该类认为菜单/工具栏项在主机程序执行与之关联的命令时是有效“选中”的。 - 同样在2023年7月17日,
MDICommandHandler
构造函数的3个重载已合并为一个,以便允许StatusLabel
参数(以及接下来的参数)成为可选参数。(如果您想手动确定状态文本的显示位置,可以指定StatusTextSource
而省略StatusLabel
。顺便说一句,新的构造函数不应该破坏任何现有代码,因为第三个参数是一个可选的Object
,如果它是Nothing
(省略)、ResourceManager
类型或Dictionary(Of String, String)
类型,都会被接受。) 截至2023年3月6日,构造函数重载注释已更新,以正确指示有3个重载。- 截至2021年12月28日,三个事件由受保护的“
On
”方法引发,以方便任何派生类进行重写。 - 截至2020年12月28日,
MDICommandHandler
的CommandDictionary
返回MDICommandInfo
字典的副本,以防止主机程序添加和删除条目;主机程序仍然可以修改条目的MDICommandInfo
属性。 - 截至2019年11月3日,该类现在会自动接入任何遇到的
ContextMenuStrip
。 以前,需要手动使用AddChildItems
来添加对上下文菜单的支持。 - 截至2019年11月1日,演示项目包含子菜单和上下文菜单。
- 截至2018年12月12日,
IsCommandEnabled
和IsCommandVisible
属性现在是可空的,这样内部(和外部)将它们设置为Nothing(有些是,有些否)的代码就不会默认将它们设置为False
(全部否)。这是我偶然才发现的一个错误! - 截至2018年2月5日,主类不再依赖于
ResourceManager
的终身访问,因此ResourceManager
属性不再存在! 命令名称和相关状态文本的列表通过构造函数或相应的GetCommandsFromResources
或GetCommandsFromDictionary
方法,由ResourceManager
或Dictionary(Of String, String)
指定。 - 截至2017年11月28日,代码已更新,以避免事件多次触发。每个
AddHandler
语句都 preceded by aRemoveHandler
statement,这样AddChildItems
的连续调用就不会创建冗余的事件处理。
Using the Code
根命名空间:MDICommandSupport
类:MDICommandHandler
(主类),MDICommandInfo
(辅助类)
MDICommandHandlerInfo类(辅助类)
构造函数
mch
是使用此辅助类的父MDICommandHandler
类的一个实例CommandName
是一个String
,用于表示程序动作的类型(对应ToolStripItem
的Tag.ToString
)StatusLabelText
是此命令的状态栏文本的可选String
属性
CommandItems
获取与此命令对应的ToolStripItem
的List
CommandName
获取命令的名称(在构造函数中指定)StatusLabelText
获取或设置状态栏文本(如果CommandName
与父类中当前选中的项匹配,则对StatusLabelText
的任何更改都会反映在父类任何工具栏标签中;这包括上面构造函数中的初始化。)IsCommandEnabled
(可空的布尔值)获取或设置所有ToolStripItem
的Enabled
属性(获取时,Nothing
表示有些已启用,有些已禁用)IsCommandVisible
(可空的布尔值)获取或设置所有项的Visible
属性(同样,Nothing
表示信息混杂)Parent
获取父MDICommandHandler
类的实例
方法
SetProperty
使用反射设置项的任意属性(由PropertyName
String
指定)为给定的NewValue
——当属性有参数时,可选择性地提供index()
信息。GetProperty
使用反射获取一个Dictionary(Of ToolstripItem, Object)
集合,其中包含任意属性(由PropertyName
String
指定)的值——当属性有参数时,可选择性地提供index()
信息。该字典中的ToolStripItem
代表字典的键;每个项给定属性的相应值代表字典的值。Contains
检查控件ToolStripItem
是否在CommandItems
List
中。Add
和Remove
分别允许您插入或删除ToolStripItem
项。
MDICommandHandler类(主类)
构造函数
MDIParentForm
是任何WinFormsForm
(不必是MDI父窗体)StatusLabel
是一个可选的ToolStripStatusLabel
,用于显示状态栏文本。如果省略该参数,则您需要手动将文本分配给标签或其他控件- StatusTextSource 分别是可选的
ResourceManager
对象或Dictionary(Of String, String)
对象,其中包含描述性命令名称/状态文本关联的列表——使用资源(资源名称指定命令名称,下划线代表空格,并附加“_Tag
”;资源string
指定状态文本),或字典(键指定命令名称;值指定状态文本)——用于每个命令的状态栏文本。如果省略该参数,您需要手动生成将分配给MDICommandInfo
实例的StatusLabelText
属性以及分配给StatusLabel
(或您自己的状态控件)的String
。(您还需要手动生成任何具有非空命令名称(在Tag.ToString
值中指定)但命令字符串不存在于资源/字典对象中的菜单/工具栏项的描述性状态文本。)
属性
MDIParentForm
获取窗体(在构造函数中指定)StatusLabel
获取或设置状态栏控件(设置为Nothing
以手动处理标签控件和/或描述文本。)SelectedItem
获取与当前选中的菜单或工具栏项对应的ToolStripItem
(如果没有当前选中项,则为Nothing)。SelectedCommand
获取当前选中ToolStripItem
的命令名称(如果未选择,则为null字符串,或者菜单/工具栏项未在其Tag.ToString
中分配值)。CommandNameForItem
获取指定ToolStripItem
的命令名称String
——基本上是该项的Tag.ToString
值。如果不存在这样的值(表明该项不应由该类处理),则返回null字符串。CommandStatusText
获取或设置由可选命令名称String
或ToolStripItem
实例指定的菜单/工具栏项的描述性状态文本;如果两者均未指定,则读取时返回当前选中项(如果存在)的状态文本。如果CommandDictionary
中未为给定命令指定状态文本,则返回null字符串。设置时,新的状态文本String
会替换给定命令的CommandDictionary
条目中的当前文本——以及StatusLabel
的当前内容(如果给定命令是当前选中的项);如果1)未提供参数且当前未选中菜单/工具栏项,或2)参数是ToolStripItem
但它没有命令名称(Tag.ToString
为null
),则会引发异常。CommandInfo
获取与命令名称String
或ToolStripItem
实例指定的动作对应的MDICommandInfo
实例。这是默认属性。CommandDictionary
获取一个Dictionary
,包含MDICommandInfo
实例,代表所有参与的命令。键是命令名称String
;值是状态文本对应的String
MDICommandInfo
实例。对由此属性返回的字典的添加和删除不会影响内部字典;调用条目的成员则会。
方法
AddItem
将ToolStripItem
添加到命令字典;如果该项是ToolStripDrownDownItem
,则调用AddChildItems
来处理下拉列表。AddChildItems
添加Control
、ControlCollection
或ToolStripItemCollection
内部的所有ToolStripItem
;此方法是递归的,并且在构造函数中自动为整个窗体调用。GetCommandsFromResources
或GetCommandsFromDictionary
分别使用ResourceManager
实例或Dictionary(Of String, String)
实例获取命令名称/状态文本关联的列表。资源名称/字典键指定命令名称,资源字符串/字典值指定命令的状态文本。第一个参数,无论是ResourceManager
还是StatusTextDictionary
,都是资源管理器/字典实例,可选的第二个参数Clear
(默认为False
)指定是首先清空命令字典(True
)还是在找到项已存在于其中时(False
)仅更改状态文本。
事件
CommandClicked
——当一个参与的ToolStripItem
(Tag.ToString
不为null
)被点击时触发CommandSelected
——当它被选中(高亮)时触发。CommandDeselected
——当它被取消选中时触发CommandHelpRequested
(新增!) ——当对其发出帮助请求(例如,使用F1或fn + F1)时触发MDICommandHandlerEventArg
参数(CommandClicked
、CommandSelected
和CommandDeselected
事件)e.CommandName
= 命令名称e.CommandItem
= 相关的特定ToolStripItem
(e.CommandName = e.CommandItem.Tag.ToString
)
-
MDICommandHandlerHelpEventArg
(新增!) 参数(CommandHelpRequested
事件)e.CommandName
= 命令名称e.CommandItem
= 相关的特定ToolStripItem
(e.CommandName = e.CommandItem.Tag.ToString
)e.MousePos
= 鼠标位置e.Handled
= 如果事件要由事件过程独占处理,则为True
,否则为False
(默认),Windows会进一步处理;此属性是可读写的
注释
- 搜索窗体或控件的菜单和工具提示时,不再跳过上下文菜单。以前,需要使用主类的
AddChildItems
方法,并将上下文菜单实例作为其参数。现在,任何具有非空ContextMenuStrip
的窗体/控件及其子控件都将被包含在搜索中。 - 如果您希望某个单独的
ToolStrip
项被排除在字典之外,请将其Tag.ToString
值留空null
。 - 任何状态栏文本的显示或隐藏都发生在
CommandClicked
、CommandSelected
或CommandDeselected
事件触发之前。 - 如果通过
ResourceManager
指定命令名称和状态文本的关联,则资源键名称必须与命令名称(Tag.ToString
)匹配,所有空格替换为下划线(“_
”),并附加“_Tag
”——即,命令“This Command
”必须有一个由键字符串“This_Command_Tag
”指定的资源名称。此规则不适用于通过Dictionary
指定关联时(即,“This Command
”具有字典键值“This Command
”)。 - 如果在实例化
MDICommandHandler
之后或最后一次调用GetCommandsFromResources
/GetCommandsFromDictionary
之后,命令的MDICommandInfo
实例的StatusLabelText
属性被设置为非空String
,那么StatusLabelText
值将覆盖该命令的任何预定义文本。 - 每当调用构造函数、
AddItem
或AddChildItems
时,所有Tag.ToString
值不为Nothing
也不为空字符串的ToolStripItem
都会被放入CommandDictionary
中,键为Tag.ToString
指定的命令名称——无论这些命令是否实际在构造函数、GetCommandsFromResources
或GetCommandsFromDictionary
中通过资源管理器/字典对象覆盖。如果找到一个命令,但它并未在资源列表/字典中指定,那么它的初始状态文本值为null字符串。 - 如果构造函数中指定的窗体为
Nothing
,或者包含了StatusTextSource
但类型错误,则会引发异常。 - 如果在当前项的
CommandClicked
事件的处理代码仍在执行时选中了新项,那么新选择在处理完现有项的取消选中之前不会被注意到;如果在现有项的CommandClicked
事件处理代码仍在执行时取消选中了该项,那么直到CommandClick
事件代码完成后才会处理其取消选中。即使在CommandClick
事件代码执行DoEvents
时,这些新规则也适用!最后,如果一个项是在没有被正式“选中”的情况下调用的——即,按下菜单项的快捷键——那么该项的CommandClick
事件仍然会在CommandSelected
之后,并在CommandDeselected
之前触发。总而言之,给定菜单/工具栏项的事件顺序现在总是CommandSelected
,然后是CommandClicked
(如果用户选择了它),最后是CommandDeselected
。这种新行为确保了在CommandClicked
的宿主事件代码中通过代码触发的项的选择/取消选择——例如,显示另一个窗体——不会使当前命令的信息无效,并且即使通过快捷键触发,项的描述性状态文本也会显示。
Imports MDICommandSupport
' constructor
Dim mch As MDICommandHandler = _
New MDICommandHandler(Me, StatusLabel, Resource3)
Dim mch As MDICommandHandler = _
New MDICommandHandler(Me, StatusLabel, Dictionary1)
' properties
mch.CommandInfo("Save").IsCommandEnabled = ShouldWeSave
Dim CanWeSave As Boolean? = mch.CommandInfo("Save").IsCommandEnabled
mch("Open").SetProperty("ForeColor", Color.Red) ' CommandInfo is default property
Dim BackColors As Dictionary(Of ToolStripItem, Object) = _
mch("Open").GetProperty("BackColor")
mch("New").StatusLabelText = "THIS COMMAND has manually set text"
Dim CurrentCommand As String = mch.SelectedCommand
' methods
mch.GetCommandsFromDictionary(Dictionary2, False)
mch.AddChildItems(Me) : mch.AddChildItems(Me.ContextMenuStrip) ' more controls
' events
AddHandler mch.CommandClicked, AddressOf mch_CommandClicked
Private Sub mch_CommandClicked(sender As Object, e As MDICommandHandlerEventArgs)
' preliminary stuff
SomeBeginningCode()
' command-specific stuff
Select Case e.CommandName
Case "Open"
OpenFileProc()
Case "New"
NewFileProc()
Case "Close"
CloseFileProc()
Case "Save"
SaveFileProc()
End Select
' final stuff
SomeEndingCode()
End Sub