Wie erhalte ich ALLE untergeordneten Steuerelemente eines Windows Forms-Formulars eines bestimmten Typs (Schaltfläche / Textfeld)?


120

Ich muss alle Steuerelemente in einem Formular vom Typ x erhalten. Ich bin mir ziemlich sicher, dass ich diesen Code in der Vergangenheit einmal gesehen habe, der so etwas verwendet hat:

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

Ich weiß, dass ich alle Steuerelemente durchlaufen kann, um Kinder mithilfe einer rekursiven Funktion dazu zu bringen, aber gibt es etwas Einfacheres oder Einfacheres, vielleicht das Folgende?

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

Antworten:


232

Hier ist eine weitere Option für Sie. Ich habe es getestet, indem ich eine Beispielanwendung erstellt habe. Dann habe ich eine GroupBox und eine GroupBox in die ursprüngliche GroupBox eingefügt. In die verschachtelte GroupBox habe ich 3 TextBox-Steuerelemente und eine Schaltfläche eingefügt. Dies ist der Code, den ich verwendet habe (enthält sogar die Rekursion, nach der Sie gesucht haben)

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

Um es im Formularladeereignis zu testen, wollte ich alle Steuerelemente in der anfänglichen GroupBox zählen

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

Und es gab jedes Mal die richtige Anzahl zurück, also denke ich, dass dies perfekt für das funktioniert, wonach Sie suchen :)


21
GetAll () hier definiert ist ein sehr guter Kandidat für eine Erweiterungsmethode zur Klasse Control
Michael Bahig

Mir hat gefallen, wie Sie Lambda-Ausdrücke verwendet haben. Wo kann man Lambda-Ausdrücke im Detail lernen?
Aditya Bokade

"'System.Windows.Forms.Control.ControlCollection' enthält keine Definition für 'Cast' und es konnte keine Erweiterungsmethode 'Cast' gefunden werden, die ein erstes Argument vom Typ 'System.Windows.Forms.Control.ControlCollection' akzeptiert Vermissen Sie eine using-Direktive oder eine Assembly-Referenz?) "Ich bin in .NET 4.5 und" Controls "hat keine" Cast "-Funktion / -Methode / was auch immer. Was vermisse ich?
Soulblazer

2
@soulblazer System.Linq-Namespace hinzufügen.
Ivan-Mark Debono

var allCtl = GetAll (this.FindForm (), typeof (TextBox)); // Dies ist ein Usercontrol, das nichts zurückgibt !!
bh_earth0

33

In C # (da Sie es als solches markiert haben) können Sie einen LINQ-Ausdruck wie folgt verwenden:

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

Zur Rekursion bearbeiten:

In diesem Beispiel erstellen Sie zuerst die Liste der Steuerelemente und rufen dann eine Methode zum Auffüllen auf. Da die Methode rekursiv ist, gibt sie die Liste nicht zurück, sondern aktualisiert sie nur.

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

Es kann möglich sein, dies in einer LINQ-Anweisung mit der DescendantsFunktion zu tun , obwohl ich damit nicht so vertraut bin. Weitere Informationen hierzu finden Sie auf dieser Seite .

Bearbeiten Sie 2, um eine Sammlung zurückzugeben:

Wie @ProfK vorgeschlagen hat, ist eine Methode, die einfach die gewünschten Steuerelemente zurückgibt, wahrscheinlich eine bessere Vorgehensweise. Um dies zu veranschaulichen, habe ich den Code wie folgt geändert:

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

Danke, C # oder VB sind in Ordnung für mich. Das Problem ist jedoch, dass Controls.OfType <TExtbox> nur die untergeordneten Elemente des aktuellen Steuerelements (in meinem Fall das Formular) zurückgibt und ich in einem einzigen Aufruf ALLE Steuerelemente in der Forma "rekursiv" abrufen möchte (Kinder, Unterkinder) , Unter-Unter-Kinder, .....) in einer einzigen Sammlung.
Luis

Ich würde erwarten, dass eine Methode namens GetAllControls eine Sammlung von Steuerelementen zurückgibt, die ich ControlList zuweisen würde. Scheint einfach besser zu üben.
ProfK

@ProfK Ich stimme dir zu; Beispiel entsprechend ändern.
JYelton

13

Dies ist eine verbesserte Version des rekursiven GetAllControls (), die tatsächlich auf privaten Variablen funktioniert:

    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

Ich habe einige der vorherigen Ideen zu einer Erweiterungsmethode zusammengefasst. Die Vorteile hierbei sind, dass Sie die korrekt eingegebene Aufzählung zurückerhalten und die Vererbung von korrekt behandelt wird 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

Sie können dazu eine LINQ-Abfrage verwenden. Dadurch wird alles in dem Formular abgefragt, das vom Typ TextBox ist

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

Danke, aber das gleiche Problem wie die Antwort, es gibt nur die Chidls zurück, aber nicht die Unterkinder usw., und ich möchte alle Ensted-Steuerelemente. Ich bin mir ziemlich sicher, dass ich gesehen habe, dass es mit einem einzigen Methodenaufruf möglich ist, der in .NET 3.5 oder 4.0 neu ist. Denken Sie daran, dass ich das irgendwo in einer Demo gesehen habe
Luis

Wenn Sie den Mangel an Rekursion ignorieren, würden Sie nicht var c = this.Controls.OfType<TextBox>()das gleiche Ergebnis erzielen?
CoderDennis

2
@ Tennis: Ja, das ist (normalerweise) eine Frage der Präferenz. Unter stackoverflow.com/questions/214500/… finden Sie eine interessante Diskussion zu diesem Thema.
JYelton

5

Es mag die alte Technik sein, aber es funktioniert wie Charme. Ich habe die Rekursion verwendet, um die Farbe aller Beschriftungen des Steuerelements zu ändern. Es funktioniert großartig.

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

Ich möchte die Antwort von PsychoCoders ändern: Da der Benutzer alle Steuerelemente eines bestimmten Typs erhalten möchte, können wir Generika folgendermaßen verwenden:

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

Auf diese Weise können wir die Funktion wie folgt aufrufen:

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

Dies ist meiner Meinung nach die beste (und laut meinen Tests schnellste) Lösung auf dieser Seite. Aber ich würde vorschlagen, dass Sie Steuerelemente in ein Array ändern: var enumerable = Steuerelemente als Steuerelement [] ?? controls.ToArray (); und ändern Sie dann zu: return enumerable.SelectMany (FindControls <T>) .Concat (enumerable) .Where (c => c.GetType () == typeof (T)). Cast <T> ();
Randall Flagg

Ist es nicht effizienter, die .OfType<T>()Linq-Methode .Where(c => c.GetType() == typeof(T)).Cast<T>();zu verwenden, als den gleichen Effekt zu erzielen?
TheHitchenator

3

Vergessen Sie nicht, dass Sie eine TextBox auch in anderen Steuerelementen als Container-Steuerelementen verwenden können. Sie können einer PictureBox sogar eine TextBox hinzufügen.

Sie müssen also auch prüfen, ob

someControl.HasChildren = True

in jeder rekursiven Funktion.

Dies ist das Ergebnis eines Layouts zum Testen dieses Codes:

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

Versuchen Sie dies mit einer Schaltfläche und einer RichTextBox in einem Formular.

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

Reflexion verwenden:

// 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

Hier ist meine Erweiterungsmethode für die ControlVerwendung von LINQ als Anpassung der @ PsychoCoder- Version:

Stattdessen wird eine Liste mit Typen verwendet, mit der Sie nicht mehrere Anrufe benötigen GetAll, um das zu erhalten, was Sie möchten. Ich benutze es derzeit als Überlastungsversion.

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

Verwendung:

//   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

Eine saubere und einfache Lösung (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;
    }
}

Holen Sie sich alle Textfelder:

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

2

Sie können den folgenden Code verwenden

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

Hier ist meine Erweiterungsmethode. Es ist sehr effizient und faul.

Verwendung:

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

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

Der Code lautet:

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

Dies ist eine sauberere Version, die faul ist, bei Bedarf aufgezählt und abgerufen werden kann.
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;

Lambda-Ausdrücke

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

Bitte fügen Sie Ihrer Antwort mehr hinzu, um zu erklären, was passiert und wie es mit der Frage zusammenhängt.
Fencer04

0

Ich habe von @PsychoCoder geändert. Alle Steuerelemente konnten jetzt gefunden werden (einschließlich verschachtelt).

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

Dies kann funktionieren:

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

Ich denke, die Funktion zum Abrufen aller Steuerelemente, über die Sie sprechen, steht nur WPF zur Verfügung .


0

Hier ist eine getestete und funktionierende generische Lösung:

Ich habe eine große Anzahl von UpDownNumeric-Steuerelementen, einige im Hauptformular, einige in Gruppenfeldern innerhalb des Formulars. Ich möchte, dass nur das zuletzt ausgewählte Steuerelement die Hintergrundfarbe in Grün ändert, für die ich zuerst alle anderen mit dieser Methode auf Weiß gesetzt habe: (kann auch auf Enkelkinder erweitert werden)

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

Dies funktioniert nicht, wenn die Kindersteuerung eigene Kinder hat.
Soulblazer

0

Sie können dies versuchen, wenn Sie wollen :)

    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
Das einfache Posten von Code hilft dem OP wenig, sein Problem oder Ihre Lösung zu verstehen. Sie sollten fast IMMER eine Erklärung zu Ihrem Code hinzufügen.
Leigero

Die Frage sagte nichts über das Löschen des Formulars aus.
LarsTech

Ja, beantwortet "die Frage" nicht, ist aber eine schöne Ergänzung dazu. Danke dir!

0

Obwohl mehrere andere Benutzer angemessene Lösungen veröffentlicht haben, möchte ich einen allgemeineren Ansatz veröffentlichen, der möglicherweise nützlicher ist.

Dies basiert weitgehend auf JYeltons Antwort.

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

Für alle, die nach einer VB-Version von Adams C # -Code suchen, die als Erweiterung der ControlKlasse geschrieben wurde:

''' <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

HINWEIS: Ich habe BaseTypeÜbereinstimmungen für abgeleitete benutzerdefinierte Steuerelemente hinzugefügt . Sie können dies entfernen oder es sogar zu einem optionalen Parameter machen, wenn Sie dies wünschen.

Verwendung

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

0

Methode erstellen

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

Und benutze es wie

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

0

Ich bin saldy mit VB, also habe ich eine Erweiterungsmethode geschrieben. Damit werden alle Kinder und Unterkinder einer Kontrolle abgerufen

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

Dann können Sie es wie folgt verwenden, wobei "btnList" ein Steuerelement ist

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

In diesem Fall wird das ausgewählte Optionsfeld ausgewählt.


-1

Einfach:

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

Dies findet nur Steuerelemente direkt in der Steuerelementauflistung von "Me" und keine Schaltflächensteuerelemente, die sich in untergeordneten Containern befinden, wie das Poster versucht hat, durch "ALL" zu implizieren.
ChrisPBacon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.