Comment obtenir TOUS les contrôles enfants d'un formulaire Windows Forms d'un type spécifique (bouton / zone de texte)?


120

J'ai besoin d'obtenir tous les contrôles sur un formulaire de type x. Je suis à peu près sûr d'avoir vu ce code une fois dans le passé qui utilisait quelque chose comme ceci:

dim ctrls() as Control
ctrls = Me.Controls(GetType(TextBox))

Je sais que je peux parcourir tous les contrôles pour que les enfants utilisent une fonction récursive, mais y a-t-il quelque chose de plus facile ou de plus simple, peut-être comme ce qui suit?

Dim Ctrls = From ctrl In Me.Controls Where ctrl.GetType Is Textbox

Réponses:


232

Voici une autre option pour vous. Je l'ai testé en créant un exemple d'application, j'ai ensuite mis une GroupBox et une GroupBox dans la GroupBox initiale. À l'intérieur de la GroupBox imbriquée, j'ai mis 3 contrôles TextBox et un bouton. C'est le code que j'ai utilisé (inclut même la récursivité que vous recherchiez)

public IEnumerable<Control> GetAll(Control control,Type type)
{
    var controls = control.Controls.Cast<Control>();

    return controls.SelectMany(ctrl => GetAll(ctrl,type))
                              .Concat(controls)
                              .Where(c => c.GetType() == type);
}

Pour le tester dans l'événement de chargement de formulaire, je voulais un nombre de tous les contrôles à l'intérieur de la GroupBox initiale

private void Form1_Load(object sender, EventArgs e)
{
    var c = GetAll(this,typeof(TextBox));
    MessageBox.Show("Total Controls: " + c.Count());
}

Et il a renvoyé le compte correct à chaque fois, donc je pense que cela fonctionnera parfaitement pour ce que vous recherchez :)


21
GetAll () défini ici est un très bon candidat pour une méthode d'extension à la classe Control
Michael Bahig

J'ai aimé la façon dont vous avez utilisé les expressions lambda. Où apprendre les expressions lambda en détail?
Aditya Bokade

"'System.Windows.Forms.Control.ControlCollection' ne contient pas de définition pour 'Cast' et aucune méthode d'extension 'Cast' acceptant un premier argument de type 'System.Windows.Forms.Control.ControlCollection' n'a pu être trouvée (sont il vous manque une directive using ou une référence d'assembly?) "Je suis sur .NET 4.5 et" Controls "n'a pas de fonction / méthode" Cast ". Qu'est-ce que je rate?
soulblazer

2
@soulblazer Ajoutez l'espace de noms System.Linq.
Ivan-Mark Debono

var allCtl = GetAll (this.FindForm (), typeof (TextBox)); // ceci est un Usercontrol renvoie Nothing !!
bh_earth0

33

En C # (puisque vous l'avez marqué comme tel), vous pouvez utiliser une expression LINQ comme celle-ci:

List<Control> c = Controls.OfType<TextBox>().Cast<Control>().ToList();

Modifier pour la récursivité:

Dans cet exemple, vous créez d'abord la liste des contrôles, puis appelez une méthode pour la remplir. Puisque la méthode est récursive, elle ne renvoie pas la liste, elle la met simplement à jour.

List<Control> ControlList = new List<Control>();
private void GetAllControls(Control container)
{
    foreach (Control c in container.Controls)
    {
        GetAllControls(c);
        if (c is TextBox) ControlList.Add(c);
    }
}

Il peut être possible de le faire dans une instruction LINQ en utilisant la Descendantsfonction, bien que je ne sois pas aussi familier avec elle. Consultez cette page pour plus d'informations à ce sujet.

Modifier 2 pour renvoyer une collection:

Comme @ProfK l'a suggéré, une méthode qui renvoie simplement les contrôles souhaités est probablement une meilleure pratique. Pour illustrer cela, j'ai modifié le code comme suit:

private IEnumerable<Control> GetAllTextBoxControls(Control container)
{
    List<Control> controlList = new List<Control>();
    foreach (Control c in container.Controls)
    {
        controlList.AddRange(GetAllTextBoxControls(c));
        if (c is TextBox)
            controlList.Add(c);
    }
    return controlList;
}

Merci, C # ou VB me convient. Mais le problème est que Controls.OfType <TExtbox> ne renvoie que les enfants du contrôle actuel (dans mon cas, le formulaire), et je veux en un seul appel obtenir TOUS les contrôles du Forma "récursivement" (chiilds, sous-enfants , sous-sous-enfants, .....) dans une seule collection.
Luis

Je m'attendrais à ce qu'une méthode appelée GetAllControls renvoie une collection de contrôles, que j'attribuerais à ControlList. Cela semble juste une meilleure pratique.
ProfK

@ProfK Je suis d'accord avec vous; changer d'exemple en conséquence.
JYelton

13

Il s'agit d'une version améliorée de la GetAllControls () récursive qui fonctionne réellement sur les variables privées:

    private void Test()
    {
         List<Control> allTextboxes = GetAllControls(this);
    }
    private List<Control> GetAllControls(Control container, List<Control> list)
    {
        foreach (Control c in container.Controls)
        {
            if (c is TextBox) list.Add(c);
            if (c.Controls.Count > 0)
                list = GetAllControls(c, list);
        }

        return list;
    }
    private List<Control> GetAllControls(Control container)
    {
        return GetAllControls(container, new List<Control>());
    }

10

J'ai combiné un tas d'idées précédentes en une seule méthode d'extension. Les avantages ici sont que vous récupérez l'énumérable correctement tapé et que l'héritage est géré correctement par OfType().

public static IEnumerable<T> FindAllChildrenByType<T>(this Control control)
{
    IEnumerable<Control> controls = control.Controls.Cast<Control>();
    return controls
        .OfType<T>()
        .Concat<T>(controls.SelectMany<Control, T>(ctrl => FindAllChildrenByType<T>(ctrl)));
}

5

Vous pouvez utiliser une requête LINQ pour ce faire. Cela interrogera tout sur le formulaire qui est de type TextBox

var c = from controls in this.Controls.OfType<TextBox>()
              select controls;

Merci, mais le même problème que la réponse, il ne renvoie que les enfants mais pas les sous-enfants, etc., et je veux tous les contrôles intégrés. Je suis à peu près sûr d'avoir vu que c'était possible avec un seul appel de méthode qui est nouveau dans .NET 3.5 ou 4.0, rappelez-vous que j'ai vu cela dans une démo somewehre
Luis

Ignorer le manque de récursivité, ne var c = this.Controls.OfType<TextBox>()donnerait-il pas le même résultat?
CoderDennis

2
@Dennis: Oui, c'est une question de préférence (généralement). Voir stackoverflow.com/questions/214500/… pour une discussion intéressante sur le sujet.
JYelton

5

C'est peut-être l'ancienne technique, mais cela fonctionne comme du charme. J'ai utilisé la récursivité pour changer la couleur de toutes les étiquettes du contrôle. Cela fonctionne très bien.

internal static void changeControlColour(Control f, Color color)
{
    foreach (Control c in f.Controls)
    {

        // MessageBox.Show(c.GetType().ToString());
        if (c.HasChildren)
        {
            changeControlColour(c, color);
        }
        else
            if (c is Label)
            {
                Label lll = (Label)c;
                lll.ForeColor = color;
            }
    }
}

4

J'aimerais modifier la réponse de PsychoCoders: comme l'utilisateur souhaite obtenir tous les contrôles d'un certain type, nous pourrions utiliser des génériques de la manière suivante:

    public IEnumerable<T> FindControls<T>(Control control) where T : Control
    {
        // we can't cast here because some controls in here will most likely not be <T>
        var controls = control.Controls.Cast<Control>();

        return controls.SelectMany(ctrl => FindControls<T>(ctrl))
                                  .Concat(controls)
                                  .Where(c => c.GetType() == typeof(T)).Cast<T>();
    }

De cette façon, nous pouvons appeler la fonction comme suit:

private void Form1_Load(object sender, EventArgs e)
{
    var c = FindControls<TextBox>(this);
    MessageBox.Show("Total Controls: " + c.Count());
}

c'est la meilleure solution (et la plus rapide selon mes tests) à mon avis sur cette page. Mais je suggérerais que vous changiez les contrôles en un tableau: var enumerable = controls as Control [] ?? controls.ToArray (); puis changez en: return enumerable.SelectMany (FindControls <T>) .Concat (enumerable) .Where (c => c.GetType () == typeof (T)). Cast <T> ();
Randall Flagg

N'est-il pas plus efficace d'utiliser la .OfType<T>()méthode Linq plutôt que .Where(c => c.GetType() == typeof(T)).Cast<T>();d'obtenir le même effet?
TheHitchenator

3

N'oubliez pas que vous pouvez également avoir une zone de texte dans d'autres contrôles autres que les contrôles de conteneur. Vous pouvez même ajouter un TextBox à un PictureBox.

Vous devez donc également vérifier si

someControl.HasChildren = True

dans toute fonction récursive.

Voici le résultat que j'ai eu d'une mise en page pour tester ce code:

TextBox13   Parent = Panel5
TextBox12   Parent = Panel5
TextBox9   Parent = Panel2
TextBox8   Parent = Panel2
TextBox16   Parent = Panel6
TextBox15   Parent = Panel6
TextBox14   Parent = Panel6
TextBox10   Parent = Panel3
TextBox11   Parent = Panel4
TextBox7   Parent = Panel1
TextBox6   Parent = Panel1
TextBox5   Parent = Panel1
TextBox4   Parent = Form1
TextBox3   Parent = Form1
TextBox2   Parent = Form1
TextBox1   Parent = Form1
tbTest   Parent = myPicBox

Essayez ceci avec un bouton et un RichTextBox sur un formulaire.

Option Strict On
Option Explicit On
Option Infer Off

Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim pb As New PictureBox
        pb.Name = "myPicBox"
        pb.BackColor = Color.Goldenrod
        pb.Size = New Size(100, 100)
        pb.Location = New Point(0, 0)
        Dim tb As New TextBox
        tb.Name = "tbTest"
        pb.Controls.Add(tb)
        Me.Controls.Add(pb)

        Dim textBoxList As New List(Of Control)
        textBoxList = GetAllControls(Of TextBox)(Me)

        Dim sb As New System.Text.StringBuilder
        For index As Integer = 0 To textBoxList.Count - 1
            sb.Append(textBoxList.Item(index).Name & "   Parent = " & textBoxList.Item(index).Parent.Name & System.Environment.NewLine)
        Next

        RichTextBox1.Text = sb.ToString
    End Sub

    Private Function GetAllControls(Of T)(ByVal searchWithin As Control) As List(Of Control)

        Dim returnList As New List(Of Control)

        If searchWithin.HasChildren = True Then
            For Each ctrl As Control In searchWithin.Controls
                If TypeOf ctrl Is T Then
                    returnList.Add(ctrl)
                End If
                returnList.AddRange(GetAllControls(Of T)(ctrl))
            Next
        ElseIf searchWithin.HasChildren = False Then
            For Each ctrl As Control In searchWithin.Controls
                If TypeOf ctrl Is T Then
                    returnList.Add(ctrl)
                End If
                returnList.AddRange(GetAllControls(Of T)(ctrl))
            Next
        End If
        Return returnList
    End Function

End Class

2

Utilisation de la réflexion:

// Return a list with all the private fields with the same type
List<T> GetAllControlsWithTypeFromControl<T>(Control parentControl)
{
    List<T> retValue = new List<T>();
    System.Reflection.FieldInfo[] fields = parentControl.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    foreach (System.Reflection.FieldInfo field in fields)
    {
      if (field.FieldType == typeof(T))
        retValue.Add((T)field.GetValue(parentControl));
    }
}

List<TextBox> ctrls = GetAllControlsWithTypeFromControl<TextBox>(this);

2

Voici ma méthode d'extension pour Control, en utilisant LINQ, comme une adaptation de la version @PsychoCoder :

Il prend une liste de types à la place qui vous permet de ne pas avoir besoin de plusieurs appels GetAllpour obtenir ce que vous voulez. Je l'utilise actuellement comme version de surcharge.

public static IEnumerable<Control> GetAll(this Control control, IEnumerable<Type> filteringTypes)
{
    var ctrls = control.Controls.Cast<Control>();

    return ctrls.SelectMany(ctrl => GetAll(ctrl, filteringTypes))
                .Concat(ctrls)
                .Where(ctl => filteringTypes.Any(t => ctl.GetType() == t));
}

Usage:

//   The types you want to select
var typeToBeSelected = new List<Type>
{
    typeof(TextBox)
    , typeof(MaskedTextBox)
    , typeof(Button)
};

//    Only one call
var allControls = MyControlThatContainsOtherControls.GetAll(typeToBeSelected);

//    Do something with it
foreach(var ctrl in allControls)
{
    ctrl.Enabled = true;
}

2

Une solution propre et simple (C #):

static class Utilities {
    public static List<T> GetAllControls<T>(this Control container) where T : Control {
        List<T> controls = new List<T>();
        if (container.Controls.Count > 0) {
            controls.AddRange(container.Controls.OfType<T>());
            foreach (Control c in container.Controls) {
                controls.AddRange(c.GetAllControls<T>());
            }
        }

        return controls;
    }
}

Obtenez toutes les zones de texte:

List<TextBox> textboxes = myControl.GetAllControls<TextBox>();

2

Vous pouvez utiliser le code ci-dessous

public static class ExtensionMethods
{
    public static IEnumerable<T> GetAll<T>(this Control control)
    {
        var controls = control.Controls.Cast<Control>();

        return controls.SelectMany(ctrl => ctrl.GetAll<T>())
                                  .Concat(controls.OfType<T>());
    }
}

2

Voici ma méthode d'extension. C'est très efficace et c'est paresseux.

Usage:

var checkBoxes = tableLayoutPanel1.FindChildControlsOfType<CheckBox>();

foreach (var checkBox in checkBoxes)
{
    checkBox.Checked = false;
}

Le code est:

public static IEnumerable<TControl> FindChildControlsOfType<TControl>(this Control control) where TControl : Control
    {
        foreach (var childControl in control.Controls.Cast<Control>())
        {
            if (childControl.GetType() == typeof(TControl))
            {
                yield return (TControl)childControl;
            }
            else
            {
                foreach (var next in FindChildControlsOfType<TControl>(childControl))
                {
                    yield return next;
                }
            }
        }
    }

c'est une version plus propre qui est paresseuse, peut être énumérée et récupérée à la demande.
Jone Polvora


1
public List<Control> GetAllChildControls(Control Root, Type FilterType = null)
{
    List<Control> AllChilds = new List<Control>();
    foreach (Control ctl in Root.Controls) {
        if (FilterType != null) {
            if (ctl.GetType == FilterType) {
                AllChilds.Add(ctl);
            }
        } else {
            AllChilds.Add(ctl);
        }
        if (ctl.HasChildren) {
            GetAllChildControls(ctl, FilterType);
        }
    }
    return AllChilds;
}

1
   IEnumerable<Control> Ctrls = from Control ctrl in Me.Controls where ctrl is TextBox | ctrl is GroupBox select ctr;

Expressions lambda

IEnumerable<Control> Ctrls = Me.Controls.Cast<Control>().Where(c => c is Button | c is GroupBox);

Veuillez ajouter plus à votre réponse qui explique ce qui se passe et comment cela est lié à la question.
Fencer04

0

J'ai modifié de @PsychoCoder. Tous les contrôles peuvent être trouvés maintenant (y compris imbriqués).

public static IEnumerable<T> GetChildrens<T>(Control control)
{
  var type = typeof (T);

  var allControls = GetAllChildrens(control);

  return allControls.Where(c => c.GetType() == type).Cast<T>();
}

private static IEnumerable<Control> GetAllChildrens(Control control)
{
  var controls = control.Controls.Cast<Control>();
  return controls.SelectMany(c => GetAllChildrens(c))
    .Concat(controls);
}

0

Cela peut fonctionner:

Public Function getControls(Of T)() As List(Of T)
    Dim st As New Stack(Of Control)
    Dim ctl As Control
    Dim li As New List(Of T)

    st.Push(Me)

    While st.Count > 0
        ctl = st.Pop
        For Each c In ctl.Controls
            st.Push(CType(c, Control))
            If c.GetType Is GetType(T) Then
                li.Add(CType(c, T))
            End If
        Next
    End While

    Return li
End Function

Je pense que la fonction permettant d'obtenir tous les contrôles dont vous parlez n'est disponible que pour WPF .


0

Voici une solution générique testée et fonctionnelle:

J'ai un grand nombre de contrôles UpDownNumeric, certains sous la forme principale, d'autres dans des boîtes de groupe dans le formulaire. Je veux que seul le dernier contrôle sélectionné change la couleur de fond en vert, pour lequel j'ai d'abord défini tous les autres sur blanc, en utilisant cette méthode: (peut également s'étendre aux petits-enfants)

    public void setAllUpDnBackColorWhite()
    {
        //To set the numericUpDown background color of the selected control to white: 
        //and then the last selected control will change to green.

        foreach (Control cont in this.Controls)
        {
           if (cont.HasChildren)
            {
                foreach (Control contChild in cont.Controls)
                    if (contChild.GetType() == typeof(NumericUpDown))
                        contChild.BackColor = Color.White;
            }
            if (cont.GetType() == typeof(NumericUpDown))
                cont.BackColor = Color.White;
       }
    }   

Cela ne fonctionne pas si le contrôle enfant a ses propres enfants.
soulblazer

0

Vous pouvez essayer ceci si vous le souhaitez :)

    private void ClearControls(Control.ControlCollection c)
    {
        foreach (Control control in c)
        {
            if (control.HasChildren)
            {
                ClearControls(control.Controls);
            }
            else
            {
                if (control is TextBox)
                {
                    TextBox txt = (TextBox)control;
                    txt.Clear();
                }
                if (control is ComboBox)
                {
                    ComboBox cmb = (ComboBox)control;
                    if (cmb.Items.Count > 0)
                        cmb.SelectedIndex = -1;
                }

                if (control is CheckBox)
                {
                    CheckBox chk = (CheckBox)control;
                    chk.Checked = false;
                }

                if (control is RadioButton)
                {
                    RadioButton rdo = (RadioButton)control;
                    rdo.Checked = false;
                }

                if (control is ListBox)
                {
                    ListBox listBox = (ListBox)control;
                    listBox.ClearSelected();
                }
            }
        }
    }
    private void btnClear_Click(object sender, EventArgs e)
    {
        ClearControls((ControlCollection)this.Controls);
    }

1
Le simple fait de publier du code n'aide pas vraiment le PO à comprendre son problème ou votre solution. Vous devriez presque TOUJOURS inclure une sorte d'explication pour accompagner votre code.
leigero

La question ne disait rien sur la suppression du formulaire.
LarsTech

Oui, ne répond pas à "la question", mais c'est un ajout intéressant. Je vous remercie!

0

Bien que plusieurs autres utilisateurs aient publié des solutions adéquates, j'aimerais publier une approche plus générale qui pourrait être plus utile.

Ceci est largement basé sur la réponse de JYelton.

public static IEnumerable<Control> AllControls(
    this Control control, 
    Func<Control, Boolean> filter = null) 
{
    if (control == null)
        throw new ArgumentNullException("control");
    if (filter == null)
        filter = (c => true);

    var list = new List<Control>();

    foreach (Control c in control.Controls) {
        list.AddRange(AllControls(c, filter));
        if (filter(c))
            list.Add(c);
    }
    return list;
}

0
    public static IEnumerable<T> GetAllControls<T>(this Control control) where T : Control
    {
        foreach (Control c in control.Controls)
        {
            if (c is T)
                yield return (T)c;
            foreach (T c1 in c.GetAllControls<T>())
                yield return c1;
        }
    }

0
    public IEnumerable<T> GetAll<T>(Control control) where T : Control
    {
        var type = typeof(T);
        var controls = control.Controls.Cast<Control>().ToArray();
        foreach (var c in controls.SelectMany(GetAll<T>).Concat(controls))
            if (c.GetType() == type) yield return (T)c;
    }

0

Pour tous ceux qui recherchent une version VB du code C # d'Adam écrit comme une extension de la Controlclasse:

''' <summary>Collects child controls of the specified type or base type within the passed control.</summary>
''' <typeparam name="T">The type of child controls to include. Restricted to objects of type Control.</typeparam>
''' <param name="Parent">Required. The parent form control.</param>
''' <returns>An object of type IEnumerable(Of T) containing the control collection.</returns>
''' <remarks>This method recursively calls itself passing child controls as the parent control.</remarks>
<Extension()>
Public Function [GetControls](Of T As Control)(
    ByVal Parent As Control) As IEnumerable(Of T)

    Dim oControls As IEnumerable(Of Control) = Parent.Controls.Cast(Of Control)()
    Return oControls.SelectMany(Function(c) GetControls(Of T)(c)).Concat(oControls.Where(Function(c) c.GetType() Is GetType(T) Or c.GetType().BaseType Is GetType(T))
End Function

REMARQUE: j'ai ajouté la BaseTypecorrespondance pour tous les contrôles personnalisés dérivés. Vous pouvez le supprimer ou même en faire un paramètre facultatif si vous le souhaitez.

Usage

Dim oButtons As IEnumerable(Of Button) = Me.GetControls(Of Button)()

0

Créer une méthode

public static IEnumerable<Control> GetControlsOfType<T>(Control control)
{
    var controls = control.Controls.Cast<Control>();
    return controls.SelectMany(ctrl => GetControlsOfType<T>(ctrl)).Concat(controls).Where(c => c is T);
}

Et utilisez-le comme

Var controls= GetControlsOfType<TextBox>(this);//You can replace this with your control

0

J'utilise VB, donc j'ai écrit une méthode d'extension. Qui récupère tous les enfants et sous-enfants d'un contrôle

Imports System.Runtime.CompilerServices
Module ControlExt

<Extension()>
Public Function GetAllChildren(Of T As Control)(parentControl As Control) As IEnumerable(Of T)
    Dim controls = parentControl.Controls.Cast(Of Control)
    Return controls.SelectMany(Of Control)(Function(ctrl) _
        GetAllChildren(Of T)(ctrl)) _
        .Concat(controls) _
        .Where(Function(ctrl) ctrl.GetType() = GetType(T)) _
    .Cast(Of T)
End Function

End Module

Ensuite, vous pouvez l'utiliser comme, où "btnList" est un contrôle

btnList.GetAllChildren(Of HtmlInputRadioButton).FirstOrDefault(Function(rb) rb.Checked)

Dans ce cas, il sélectionnera le bouton radio sélectionné.


-1

Simplement:

For Each ctrl In Me.Controls.OfType(Of Button)()
   ctrl.Text = "Hello World!"
Next

Cela ne trouvera que les contrôles directement dans la collection de contrôles de "Moi" et ne trouvera pas les contrôles Button qui se trouvent dans des conteneurs enfants comme l'affiche essayait de l'impliquer par "ALL".
ChrisPBacon le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.