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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (16投票s)

2015年2月11日

CPOL

12分钟阅读

viewsIcon

33625

downloadIcon

880

简化了MDI父窗体和其他窗体中ToolStrip类型菜单和工具栏的操作;同时也适用于ToolStrip类型上下文菜单。还处理菜单和工具栏项的帮助请求。

 

最近更新-- 2024年9月9日 晚上9:50 (EST)

引言

每当我创建一个大型应用程序时——尤其是MDI应用程序——我经常会有一些命令(程序动作),用户可以在多个地方调用它们——即,从菜单工具栏。这可能意味着MDI模块中有两个执行相同功能的子程序——以及需要为每个ToolStripItem(菜单和工具栏项)设置相似的属性——例如,当某个命令需要启用/禁用或显示/隐藏时。此外,每个命令执行的代码,虽然整体不同,但常常包含某些通用指令——比如,在子程序的开头和结尾。最后,我经常想检测何时ToolStripItem被选中(高亮)或取消选中(取消高亮)——例如,为了在状态栏标签中显示文本;不幸的是,菜单项在选中/取消选中时不会引发特定的事件

下面的类库会扫描窗体(任何窗体,不只是MDI窗体)的控件集合,查找MenuStrips、ToolStrips以及包含ToolStripItems的ToolStripItem容器,并通过命令名称(由Tag属性的ToString方法指定)将这些项分组到一个“命令字典”中,该字典存在于MDICommandInfo类的实例中——这允许一次性指定状态栏文本并获取/设置与给定命令关联的所有ToolStripItem的属性。

MDICommandInfo实例由另一个类MDICommandHandler管理——它会拦截所需的鼠标/键盘事件,以便检测命令何时被调用(点击)、选中和取消选中——并将这些情况委托给三个事件:CommandClickedCommandSelectedCommandDeselected。命令名称和相关状态文本的列表由用户通过ResourceManagerDictionary手动提供。

它也可以用于ContextMenuStrip

(新增!) 它现在还通过事件CommandHelpRequested处理选中参与的菜单/工具栏项时的帮助请求。

(新增!) 演示程序在窗体的状态栏以及Debug窗口中显示帮助请求的通知。

历史记录

从最新到最旧

  1. 截至2024年9月9日晚上9:50 EST,演示程序会在窗体的状态栏以及Debug窗口(现在也只用于记录菜单/工具栏项的选择和取消选择)中显示帮助请求时的通知。
  2. 截至2024年9月9日凌晨1:40 EST,提供了一个新事件CommandHelpRequested,以使MDICommandHandler能够处理任何参与的菜单或工具栏项的应用程序帮助请求。此外,“第一个命令”的快捷键现在是Shift + F1,以便使用标准的帮助键(F1fn + F1)来触发帮助请求。
  3. (BUG修复!) 截至2023年7月27日下午2:40 EST,我修复了一个bug,以防止CommandClicked事件的重入问题。
  4. (BUG修复!) 截至2023年7月18日下午4:35 EST,我修改了MDICommandInfo类,使其接受一个额外的参数——其父MDICommandHander类的实例——这样,当前者的一个实例的StatusLabelText属性被更改时,如果前者的命令名称与后者当前菜单/工具栏项的命令名称匹配,则后者(父类)中的标签会被更新。这不应该破坏您当前的代码,前提是您没有在主机代码中实例化MDICommandInfo(而不是MDICommandHandler)。(如果已经实例化,请在每个“New”构造函数语句中插入父类的实例。)此修复确保了例如使用MDICommandHandlerCommandInfo(默认)属性所做的对StatusLabelText的更改会立即反映在UI中。
  5. 截至2023年7月18日,CommandStatusText属性现在是可读写的,而不是只读的。设置此属性时,新的标签字符串会放入CommandDictionary中,如果指定的命令是当前选中的项,则会放入相应的工具栏标签中。
  6. 截至2023年7月17日,在任何主机程序事件代码的CommandClicked事件运行时,CommandSelectedCommandDeselected事件都会被抑制——并且即使菜单项是通过快捷键调用的,它们也保证分别在CommandClicked之前和之后触发——以确保该类认为菜单/工具栏项在主机程序执行与之关联的命令时是有效“选中”的。
  7. 同样在2023年7月17日,MDICommandHandler构造函数的3个重载已合并为一个,以便允许StatusLabel参数(以及接下来的参数)成为可选参数。(如果您想手动确定状态文本的显示位置,可以指定StatusTextSource而省略StatusLabel。顺便说一句,新的构造函数不应该破坏任何现有代码,因为第三个参数是一个可选的Object,如果它是Nothing(省略)、ResourceManager类型或Dictionary(Of String, String)类型,都会被接受。)
  8. 截至2023年3月6日,构造函数重载注释已更新,以正确指示有3个重载。
  9. 截至2021年12月28日,三个事件由受保护的“On”方法引发,以方便任何派生类进行重写。
  10. 截至2020年12月28日,MDICommandHandlerCommandDictionary返回MDICommandInfo字典的副本,以防止主机程序添加和删除条目;主机程序仍然可以修改条目的MDICommandInfo属性。
  11. 截至2019年11月3日,该类现在会自动接入任何遇到的ContextMenuStrip 以前,需要手动使用AddChildItems来添加对上下文菜单的支持。
  12. 截至2019年11月1日,演示项目包含子菜单和上下文菜单。
  13. 截至2018年12月12日,IsCommandEnabledIsCommandVisible属性现在是可空的,这样内部(和外部)将它们设置为Nothing(有些是,有些否)的代码就不会默认将它们设置为False(全部否)。这是我偶然才发现的一个错误!
  14. 截至2018年2月5日,主类不再依赖于ResourceManager的终身访问,因此ResourceManager属性不再存在! 命令名称和相关状态文本的列表通过构造函数或相应的GetCommandsFromResourcesGetCommandsFromDictionary方法,由ResourceManagerDictionary(Of String, String)指定。
  15. 截至2017年11月28日,代码已更新,以避免事件多次触发。每个AddHandler语句都 preceded by a RemoveHandler statement,这样AddChildItems的连续调用就不会创建冗余的事件处理。

Using the Code

根命名空间:MDICommandSupport

类:MDICommandHandler(主类),MDICommandInfo(辅助类)

MDICommandHandlerInfo类(辅助类)

构造函数
  • mch是使用此辅助类的父MDICommandHandler类的一个实例
  • CommandName 是一个String,用于表示程序动作的类型(对应ToolStripItemTag.ToString
  • StatusLabelText 是此命令的状态栏文本的可选String
属性
  • CommandItems获取与此命令对应的ToolStripItemList
  • CommandName获取命令的名称(在构造函数中指定)
  • StatusLabelText获取或设置状态栏文本(如果CommandName与父类中当前选中的项匹配,则对StatusLabelText的任何更改都会反映在父类任何工具栏标签中;这包括上面构造函数中的初始化。)
  • IsCommandEnabled(可空的布尔值)获取或设置所有ToolStripItemEnabled属性(获取时,Nothing表示有些已启用,有些已禁用)
  • IsCommandVisible(可空的布尔值)获取或设置所有项的Visible属性(同样,Nothing表示信息混杂)
  • Parent获取父MDICommandHandler类的实例
方法
  • SetProperty使用反射设置项的任意属性(由PropertyName String指定)为给定的NewValue——当属性有参数时,可选择性地提供index()信息。
  • GetProperty使用反射获取一个Dictionary(Of ToolstripItem, Object)集合,其中包含任意属性(由PropertyName String指定)的值——当属性有参数时,可选择性地提供index()信息。该字典中的ToolStripItem代表字典的;每个项给定属性的相应值代表字典的
  • Contains检查控件ToolStripItem是否在CommandItems List中。
  • AddRemove分别允许您插入或删除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获取或设置由可选命令名称StringToolStripItem实例指定的菜单/工具栏项的描述性状态文本;如果两者均未指定,则读取时返回当前选中项(如果存在)的状态文本。如果CommandDictionary中未为给定命令指定状态文本,则返回null字符串。设置时,新的状态文本String会替换给定命令的CommandDictionary条目中的当前文本——以及StatusLabel的当前内容(如果给定命令是当前选中的项);如果1)未提供参数且当前未选中菜单/工具栏项,或2)参数是ToolStripItem但它没有命令名称(Tag.ToStringnull),则会引发异常。
  • CommandInfo获取与命令名称String ToolStripItem实例指定的动作对应的MDICommandInfo实例。这是默认属性
  • CommandDictionary获取一个Dictionary,包含MDICommandInfo实例,代表所有参与的命令。键是命令名称String;值是状态文本 String对应的MDICommandInfo实例。对由此属性返回的字典的添加和删除不会影响内部字典;调用条目的成员则会。
方法
  • AddItemToolStripItem添加到命令字典;如果该项是ToolStripDrownDownItem,则调用AddChildItems来处理下拉列表。
  • AddChildItems添加ControlControlCollectionToolStripItemCollection内部的所有ToolStripItem;此方法是递归的,并且在构造函数中自动为整个窗体调用。
  • GetCommandsFromResources 或 GetCommandsFromDictionary分别使用ResourceManager实例或Dictionary(Of String, String)实例获取命令名称/状态文本关联的列表。资源名称/字典键指定命令名称,资源字符串/字典值指定命令的状态文本。第一个参数,无论是ResourceManager还是StatusTextDictionary,都是资源管理器/字典实例,可选的第二个参数Clear(默认为False)指定是首先清空命令字典(True)还是在找到项已存在于其中时(False)仅更改状态文本。
事件
  • CommandClicked——当一个参与的ToolStripItemTag.ToString不为null)被点击时触发
  • CommandSelected——当它被选中(高亮)时触发。
  • CommandDeselected——当它被取消选中时触发
  • CommandHelpRequested (新增!) ——当对其发出帮助请求(例如,使用F1fn + F1)时触发

    MDICommandHandlerEventArg参数(CommandClickedCommandSelectedCommandDeselected事件)

    • e.CommandName = 命令名称
    • e.CommandItem = 相关的特定ToolStripIteme.CommandName = e.CommandItem.Tag.ToString
  • MDICommandHandlerHelpEventArg (新增!) 参数(CommandHelpRequested事件)

    • e.CommandName = 命令名称
    • e.CommandItem = 相关的特定ToolStripIteme.CommandName = e.CommandItem.Tag.ToString
    • e.MousePos = 鼠标位置
    • e.Handled = 如果事件要由事件过程独占处理,则为True,否则为False(默认),Windows会进一步处理;此属性是可读写的

注释

  1. 搜索窗体或控件的菜单和工具提示时,不再跳过上下文菜单。以前,需要使用主类的AddChildItems方法,并将上下文菜单实例作为其参数。现在,任何具有非空ContextMenuStrip的窗体/控件及其子控件都将被包含在搜索中。
  2. 如果您希望某个单独的ToolStrip项被排除在字典之外,请将其Tag.ToString值留空null
  3. 任何状态栏文本的显示或隐藏都发生在CommandClickedCommandSelectedCommandDeselected事件触发之前
  4. 如果通过ResourceManager指定命令名称和状态文本的关联,则资源键名称必须与命令名称(Tag.ToString)匹配,所有空格替换为下划线(“_”),并附加“_Tag”——即,命令“This Command”必须有一个由键字符串“This_Command_Tag”指定的资源名称。此规则适用于通过Dictionary指定关联时(即,“This Command”具有字典键值“This Command”)。
  5. 如果在实例化MDICommandHandler之后或最后一次调用GetCommandsFromResources / GetCommandsFromDictionary之后,命令的MDICommandInfo实例的StatusLabelText属性被设置为非空String,那么StatusLabelText值将覆盖该命令的任何预定义文本。
  6. 每当调用构造函数、AddItemAddChildItems时,所有Tag.ToString值不为Nothing也不为空字符串的ToolStripItem都会被放入CommandDictionary中,键为Tag.ToString指定的命令名称——无论这些命令是否实际在构造函数、GetCommandsFromResourcesGetCommandsFromDictionary中通过资源管理器/字典对象覆盖。如果找到一个命令,但它并未在资源列表/字典中指定,那么它的初始状态文本值为null字符串。
  7. 如果构造函数中指定的窗体为Nothing,或者包含了StatusTextSource但类型错误,则会引发异常。
  8. 如果在当前项的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
© . All rights reserved.