在 C# 的 Windows 窗体中查找组件(不是控件)

17

我知道如何找到并收集Windows表单中使用的所有控件的列表。类似这样:

static public void FillControls(Control control, List<Control> AllControls)
{
    String controlName = "";
    controlName = control.Name;

    foreach (Control c in control.Controls)
    {
        controlName = c.Name;
        if ((control.Controls.Count > 0))
        {
            AllControls.Add(c);
            FillControls(c, AllControls);
        }
    }
}

然而,这个函数无法检索到表单底部的非可视组件,例如HelpProvider、ImageList、TableAdapters、DataSets等。

有没有办法获取这些组件的列表?

编辑:

感谢@HighCore指向使用System.ComponentModel.Component代替类似函数,在此函数中可以获取诸如ImageList、Help Provider和BindingSource之类的组件列表。 但是,我仍然错过了TableAdapters和DataSets。我想因为它们直接从Object继承。

。不要参考旧帖子,那些只获取控件列表的类似函数。

编辑:为什么会有负面票数?这个问题以前从未得到过答案!


我不熟悉winforms,但如果你要找的是“组件”(即System.ComponentModel.Component),那么你应该使用它而不是Control。顺便说一句,请将这个可怕的foreach代码改成一个漂亮的.SelectMany() - Federico Berasategui
http://stackoverflow.com/questions/6736914/how-to-access-find-all-controls-and-all-components-into-form-in-c - Mert Akkaya
@HighCore 在这里使用 SelectMany 并不是很有帮助。主要问题在于它将结果添加到作为参数传递的列表中,而不是从设计角度将它们作为结果产生。如果需要,您还可以删除递归并使用显式堆栈。在这里使用 foreach 也不是不合适的。 - Servy
不,这与你所指出的不是重复的。我正在询问非可视组件,而不是控件。那篇帖子上的问题提到了“组件”,但解决方案只涉及获取控件列表。 - Craig Stevensson
3个回答

17

惊人的是,似乎唯一的方法是通过反射来实现。

private IEnumerable<Component> EnumerateComponents()
{
    return from field in GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
           where typeof (Component).IsAssignableFrom(field.FieldType)
           let component = (Component) field.GetValue(this)
           where component != null
           select component;
}

1
抱歉,我知道这个帖子很旧了,但我很惊讶这是获取表单组件的唯一(且不太直观)方法。感谢Craig提出问题,感谢Michael提供答案。这对我帮助很大。 - nurchi
@Smith 取决于您要递归的内容。是组件中的组件?子控件的组件?还是拥有窗口的组件? - Michael Gunter
@MichaelGunter menuitems(菜单项),toolstripbuttons(工具条按钮),buttons(按钮),labels(标签),textboxes(文本框),columnheaders(列标题)等,基本上是任何其文本可以本地化的控件。 - Smith
@Smith 或许有一种方法可以列举出 所有 可设计的组件(就像 Visual Studio 本身那样),但这可能会很复杂且容易出错。如果你想要枚举出在设计器中具有名称的所有对象,你可以按照我在这个答案中展示的方式做 -- 只需要删除 where 子句和对 (Component) 的转换即可。 - Michael Gunter
@MichaelGunter 有没有办法在设计时(WinForms)使其工作? - Jenny
@Jenny 我不知道你在问什么。你能描述一下你的使用情况吗? - Michael Gunter

2

通过设计器构建的所有控件必须具有名为“components”的IContainer类型的私有字段。如果存在该字段,您可以使用反射来获取其值,然后遍历组件。

此方法与其他答案不同之处在于,它仅返回使用设计器添加到表单中的组件,而不是所有可转换为Component的字段。

    public IEnumerable<Component> GetComponents(Control c)
    {
        FieldInfo fi = c.GetType()
            .GetField("components", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        if (fi?.GetValue(c) is IContainer container)
        {
            return container.Components.OfType<Component>();
        }
        else
        {
            return Enumerable.Empty<Component>();
        }
    }

你,先生/女士是一个救命恩人。 - stigzler
这种方法的问题在于,在您的表单/控件具有基类的情况下,获取继承的私有“components”字段。每个继承层都会添加一个新的私有“components”字段以获取。这对解决该问题没有任何帮助。 - Bryan W

0

有一种比反射更干净的方式可以从表单中获取组件。表单本身是一个组件容器,因此它实现了 IContainer 接口非常好,我甚至不明白为什么不是这样做。

将其转换为 IContainer 后,您不仅可以检索设计人员生成的所有组件,而且作为奖励,您还可以添加/删除自己的组件,遵守表单 Dispose() 机制。

public partial class MyForm : Form, IContainer
{

    // ...

    public void Add(IComponent component) => this.components.Add(component);

    public void Add(IComponent component, string name) => this.components.Add(component, name);

    public void Remove(IComponent component) => this.components.Remove(component);

    public ComponentCollection Components => components.Components;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接