【问题标题】:Getting all ContextMenuStrips获取所有 ContextMenuStrips
【发布时间】:2022-01-10 11:24:09
【问题描述】:

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

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

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

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 是 UserControl。

主题类被许多其他对象(窗体、用户控件)使用,因此很难为这个用户控件拥有一个特定的属性。如何获取 UserControl 上所有 ContextMenuStrips 的列表?

【问题讨论】:

  • components 是表单的私有成员字段。所以需要使用反射来获取。

标签: .net vb.net winforms user-controls contextmenustrip


【解决方案1】:

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

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

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

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

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

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;
        }
    }
}

【讨论】:

  • 没问题@stigzler 我还发布了另一个解决方案;然而另一个也是一个hacky解决方案,依赖于内部属性和反射(这不是一个很好的想法,但效果很好)。
【解决方案2】:

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


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

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

ToolStripManager 有一个internal propertyToolStrips,它保留对您在应用程序中拥有的所有 ToolStrip 实例(包括 ToolStrip、MenuStrip、ContextMenuStrip、StatusStrip)的引用。事实上,当您创建这些组件的实例时,它们是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>();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-16
    • 2012-07-02
    • 2014-01-04
    • 2018-01-13
    • 2021-09-19
    • 2019-06-13
    • 2020-10-04
    相关资源
    最近更新 更多