17

I know how to find and collect a list of all the controls used in a Windows Form. Something like this:

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

However this function does not retrieve the non-visual components on the bottom of the form like the HelpProvider, ImageList, TableAdapters, DataSets, etc.

Is there a way to get the list of these components as well?

Edit:

Thanks @HighCore for pointing me to use System.ComponentModel.Component instead in a similar function does get me a list with components such the ImageList, the Help Provider and the BindingSource. However, I still miss from this list the TableAdapters and the DataSets. I suppose because those inherit directly from Object.

Please. Don't refer me to older posts which shows a similar function to mine and that only gets the list of the controls.

Edit: Why the negative votes? This question has never been answered before!

Craig Stevensson
  • 1,336
  • 4
  • 21
  • 43
  • I'm not familiar with winforms, but if what you're looking for are `Components` (I.E `System.ComponentModel.Component`), you should use that instead of `Control`. And BTW please change this horrible foreach code into a beautiful `.SelectMany()` – Federico Berasategui Jun 18 '13 at 14:49
  • http://stackoverflow.com/questions/6736914/how-to-access-find-all-controls-and-all-components-into-form-in-c – Mert Akkaya Jun 18 '13 at 14:54
  • @HighCore The use of `SelectMany` isn't really that helpful here. The major problem is that it's adding the results to a list passed in as a parameter, instead of yielding them as a result, from a design standpoint. You can also remove the recursion and use an explicit stack if desired. The use of a `foreach` is not inappropriate here though. – Servy Jun 18 '13 at 15:05
  • No, this is not duplicate with what you're pointing out. I'm asking for non-visual components, not for controls. The question on that post mentions "components" but the solution addresses only the get a list of controls. – Craig Stevensson Jun 18 '13 at 15:07

3 Answers3

17

Surprisingly, it seems the only way to do this is via reflection.

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;
}
Michael Gunter
  • 12,528
  • 1
  • 24
  • 58
  • 1
    Sorry, I know this thread is old, but I'm surprised this is the only (and not-so-intuitive) way of getting the components of a form. Thanks Craig for the question and Michael for the answer. This helped me a lot. – nurchi Sep 17 '15 at 16:05
  • @Smith Depends on what you're trying to recurse. Components of components? Components of child controls? Components of owned windows? – Michael Gunter Jun 25 '18 at 18:55
  • @MichaelGunter `menuitems`, `toolstripbuttons`, `buttons`, `labels`, `textboxes`, `columnheaders` etc Basically, any control whoose text can be localized – Smith Jun 28 '18 at 23:51
  • @Smith There may be a way to enumerate _all_ designable components (the way Visual Studio itself does), but it would be complicated and error prone. If you want to enumerate all the things that have a name in the designer, you can do the same thing I show here in this answer -- just remove the `where` clause and the cast to `(Component)`. – Michael Gunter Jun 29 '18 at 15:14
  • @MichaelGunter Is there a way to get this working during DesignTime (WinForms)? – Jenny Aug 15 '18 at 18:50
  • @Jenny I don't know what you're asking. Can you describe your use case? – Michael Gunter Aug 17 '18 at 18:28
2

All controls built through the designer must have a private field called "components" of type IContainer. You can use reflection to get the value of this field, if it exists, and then iterate through the components.

This method differs from the other answer in that this will only return the components added to the form using the designer, as opposed to all fields that can be cast as 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>();
        }
    }
MineR
  • 2,144
  • 12
  • 18
  • You, sir/madam are a life saver. – stigzler Dec 15 '22 at 13:05
  • The problem with this approach is getting the inherited private "components" field in the case where your Form/Control has a base class. Each layer of inheritance adds a new private "components" field to get at. This does nothing to address that issue. – Bryan W Jan 26 '23 at 16:11
0

There is a cleaner way than Reflection to get components from a form. The form itself is a component holder, so it is good that it implements IContainer, as such, I don't even understand why it wasn't done that way.

After casting it into an IContainer, not only will you be able to retrieve all the components generated by the designer, but as a bonus, you will also be able to add/remove your own components, obeying the Form Dispose() mechanism.

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;
}
Larry
  • 17,605
  • 9
  • 77
  • 106