获取所有 ContextMenuStrip

Getting all ContextMenuStrips

我有一个 UserControl,它有几个通过 VS Designer 添加的 ContextMenuStrip。它们在设计时没有分配给任何控件,因为它们被动态分配给一个“下拉”按钮,上下文 sensative.I 有一个 Class 循环遍历控件中的所有控件以设置这些控件的主题(即暗模式)。代码:

Private ControlList As List(Of Control) = New List(Of Control)()
Private Sub GetAllControls(ByVal container As Control)
    For Each c As Control In container.Controls
        GetAllControls(c)
        ControlList.Add(c)
    Next
End Sub

未检测到 ContextMenuStrip。通过阅读,我了解到这些不是控件,而是组件。我通过以下方式尝试了一种建议的解决方案:

Private Sub GetAllComponents(container As Object)
    For Each co As System.ComponentModel.Component In container.components.Components
        If TypeOf co Is ContextMenuStrip Then
            ControlList.Add(co)
        End If
    Next
End Sub

但是我在 运行 时遇到错误:

System.MissingMemberException: 'Public member 'components' on type 'ObjectSelector' not found.'

ObjectSelector 是用户控件。

主题 Class 被许多其他对象(表单、用户控件)使用,因此很难为这个 UserControl 提供特定的 属性。如何获取 UserControl 上所有 ContextMenuStrip 的列表?

当您使用设计器并在表单上放置一个组件时,设计器会从该表单创建一个 components 私有成员字段。要获得该成员,您需要依靠反射。

在我 post 代码之前,我想强调以下几点:

  • 更改 ContextMenuStripToolStripMenuStripStatusStrip 主题的正确方法是将主题渲染器分配给 ToolStripManager.Renderer
  • 所有控件的主题管理器的另一个想法是,创建一个扩展程序提供程序,它在设计时检测所有控件和组件并分配它们的属性或处理它们的事件以更改它们的主题。
  • 当您在运行时间内获取所有控件和组件时,结果中可能会有一些不需要的控件和组件;例如 属性 网格的工具条或数字 updown 的 updown 按钮。

获取控件的所有后代组件(递归)

无论如何,我会 post 一个出于学习目的的答案,向您展示如何获得控件及其子项上所有组件的列表。为此,我将创建一个扩展方法,您可以像这样使用它:

var ctxMenuStrips = this.FindForm().AllComponents().OfTypeOf<ContextMenuStrip>();

扩展方法的代码如下:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
public static class ControlExtensions
{
    public static IEnumerable<Component> AllComponents(this Control control)
    {
        var componentsFields = control.GetType().GetField("components",
            System.Reflection.BindingFlags.NonPublic |
            System.Reflection.BindingFlags.Instance);
        var components = componentsFields?.GetValue(control) as IContainer;
        if (components != null)
            foreach (Component component in components.Components)
                yield return component;

        foreach (Control child in control.Controls)
            foreach (Component c in child.AllComponents())
                yield return c;
    }
    public static IEnumerable<Control> AllControls(this Control control)
    {
        foreach (Control c in control.Controls)
        {
            yield return c;
            foreach (Control child in c.AllControls())
                yield return child;
        }
    }
}

注意:完全不同的方法,所以我将post它作为另一个答案。对于某些用户来说可能更有趣;然而,这也是一种 hacky 方式。


另一种解决方案依赖于 components 控件的私有成员来递归地获取所有组件,但是如果您只是专注于获取 ToolStrip 及其所有后代,这是另一种选择。

获取应用中的所有ToolStrip、ContextMenuStrip、MenuStrip和StatusStrip

ToolStripManager has an internal property, ToolStrips, which keeps a reference to all ToolStrip instances (including ToolStrip, MenuStrip, ContextMenuStrip, StatusStrip) that you have in your application. In fact when you create an instance of those components, they add themselves to the toolstrips collection of the toolstrip manager。您可以使用 属性.

使用反射获取所有工具条、上下文菜单条、菜单条和状态条。

代码如下:

public static IEnumerable<ToolStrip> ToolStrips()
{
    var toolStrips = (typeof(ToolStripManager).GetProperty("ToolStrips",
            System.Reflection.BindingFlags.Static |
            System.Reflection.BindingFlags.NonPublic)
            .GetValue(null, null) as System.Collections.IList);
    for (int i = 0; i < toolStrips.Count; i++)
        yield return toolStrips[i] as ToolStrip;
}

并将其限制在上下文菜单条中:

var ctxMenuStrips = ToolStrips().OfTypeOf<ContextMenuStrip>();