Come ottenere TUTTI i controlli figlio di un modulo Windows Form di un tipo specifico (pulsante / casella di testo)?


120

Devo ottenere tutti i controlli su un modulo di tipo x. Sono abbastanza sicuro di aver visto quel codice una volta in passato che usava qualcosa del genere:

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

So di poter iterare su tutti i controlli facendo in modo che i bambini utilizzino una funzione ricorsiva, ma c'è qualcosa di più semplice o più diretto, forse come il seguente?

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

Risposte:


232

Ecco un'altra opzione per te. L'ho testato creando un'applicazione di esempio, quindi ho inserito un GroupBox e un GroupBox all'interno del GroupBox iniziale. All'interno del GroupBox annidato ho inserito 3 controlli TextBox e un pulsante. Questo è il codice che ho usato (include anche la ricorsione che stavi cercando)

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

Per testarlo nell'evento di caricamento del modulo, volevo un conteggio di tutti i controlli all'interno del GroupBox iniziale

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

E ogni volta ha restituito il conteggio corretto, quindi penso che funzionerà perfettamente per quello che stai cercando :)


21
GetAll () definito qui è un ottimo candidato per un metodo di estensione alla classe Control
Michael Bahig,

Mi è piaciuto il modo in cui hai usato le espressioni lambda. Dove apprendere in dettaglio le espressioni lambda?
Aditya Bokade

"" System.Windows.Forms.Control.ControlCollection "non contiene una definizione per" Cast "e non è stato possibile trovare alcun metodo di estensione" Cast "che accetti un primo argomento di tipo" System.Windows.Forms.Control.ControlCollection "(sono ti manca una direttiva using o un riferimento all'assembly?) "Sono su .NET 4.5 e" Controls "non ha funzione / metodo / altro" Cast ". Cosa mi manca?
soulblazer

2
@soulblazer Aggiungi spazio dei nomi System.Linq.
Ivan-Mark Debono

var allCtl = GetAll (this.FindForm (), typeof (TextBox)); // questo è un Usercontrol restituisce Nothing !!
bh_earth0

33

In C # (dato che l'hai etichettato come tale) potresti usare un'espressione LINQ come questa:

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

Modifica per ricorsione:

In questo esempio, si crea prima l'elenco di controlli e quindi si chiama un metodo per popolarlo. Poiché il metodo è ricorsivo, non restituisce l'elenco, lo aggiorna semplicemente.

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

Potrebbe essere possibile eseguire questa operazione in un'istruzione LINQ utilizzando la Descendantsfunzione, anche se non ho familiarità con essa. Vedi questa pagina per maggiori informazioni su questo.

Modifica 2 per restituire una raccolta:

Come suggerito da @ProfK, un metodo che restituisce semplicemente i controlli desiderati è probabilmente una pratica migliore. Per illustrare questo ho modificato il codice come segue:

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

Grazie, C # o VB va bene per me. Ma il problema è che Controls.OfType <TExtbox> restituisce solo i figli del controllo corrente (nel mio caso il Form), e voglio in una singola chiamata ottenere TUTTI i controlli nella Forma "ricorsivamente" (chiilds, sub-childs , sub-sub-childs, .....) in un'unica raccolta.
Luis

Mi aspetterei che un metodo chiamato GetAllControls restituisse una raccolta di controlli, che assegnerei a ControlList. Sembra solo una pratica migliore.
ProfK

@ProfK sono d'accordo con te; cambiando l'esempio di conseguenza.
JYelton

13

Questa è una versione migliorata del ricorsivo GetAllControls () che funziona effettivamente su vars private:

    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

Ho combinato un sacco di idee precedenti in un unico metodo di estensione. I vantaggi qui sono che si ottiene indietro l'enumerabile digitato correttamente, inoltre l'ereditarietà viene gestita correttamente da 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

È possibile utilizzare una query LINQ per eseguire questa operazione. Ciò interrogherà tutto sul modulo che è di tipo TextBox

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

Grazie, ma lo stesso problema della risposta, restituisce solo i chidl ma non i sottochild, ecc. E voglio tutti i controlli integrati. Sono abbastanza sicuro di aver visto che è possibile con una singola chiamata di metodo che è nuova in .NET 3.5 o 4.0, ricorda che l'ho visto in una demo da qualche parte
Luis

Ignorando la mancanza di ricorsione, non var c = this.Controls.OfType<TextBox>()darebbe lo stesso risultato?
CoderDennis

2
@ Dennis: Sì, è una questione di preferenza (di solito). Vedere stackoverflow.com/questions/214500/… per un'interessante discussione sull'argomento.
JYelton

5

Potrebbe essere la tecnica antica, ma funziona come un fascino. Ho usato la ricorsione per cambiare il colore di tutte le etichette del controllo. Funziona benissimo.

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

Vorrei modificare la risposta di PsychoCoders: poiché l'utente desidera ottenere tutti i controlli di un certo tipo, potremmo utilizzare i generici nel modo seguente:

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

In questo modo, possiamo chiamare la funzione come segue:

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

questa è la soluzione migliore (e più veloce secondo i miei test) secondo me in questa pagina. Ma suggerirei di modificare i controlli in un array: var enumerable = controls as Control [] ?? controls.ToArray (); e quindi modificare in: return enumerable.SelectMany (FindControls <T>) .Concat (enumerable) .Where (c => c.GetType () == typeof (T)). Cast <T> ();
Randall Flagg

Non è più efficiente utilizzare il .OfType<T>()metodo Linq piuttosto che .Where(c => c.GetType() == typeof(T)).Cast<T>();ottenere lo stesso effetto?
TheHitchenator

3

Non dimenticare che puoi anche avere una casella di testo all'interno di altri controlli diversi dai controlli contenitore. Puoi anche aggiungere un TextBox a un PictureBox.

Quindi devi anche controllare se

someControl.HasChildren = True

in qualsiasi funzione ricorsiva.

Questo è il risultato che ho ottenuto da un layout per testare questo codice:

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

Prova questo con un pulsante e un RichTextBox su un modulo.

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

Utilizzando la riflessione:

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

Ecco il mio metodo di estensione per l' Controlutilizzo di LINQ come adattamento della versione di @PsychoCoder :

Ci vuole invece un elenco di tipi che ti permette di non aver bisogno di più chiamate GetAllper ottenere quello che vuoi. Attualmente lo uso come una versione di sovraccarico.

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

Uso:

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

Una soluzione semplice e pulita (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;
    }
}

Ottieni tutte le caselle di testo:

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

2

Puoi usare il codice sottostante

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

Ecco il mio metodo di estensione. È molto efficiente ed è pigro.

Uso:

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

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

Il codice è:

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

questa è una versione più pulita che è pigra, può essere enumerata e recuperata su richiesta.
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;

Espressioni lambda

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

Aggiungi altro alla tua risposta che spieghi cosa sta succedendo e come è correlato alla domanda.
Fencer04

0

Ho modificato da @PsychoCoder. Ora è possibile trovare tutti i controlli (includere nidificati).

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

Questo potrebbe funzionare:

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

Penso che la funzione per ottenere tutti i controlli di cui parli sia disponibile solo per WPF .


0

Ecco una soluzione generica testata e funzionante:

Ho un gran numero di controlli UpDownNumeric, alcuni nella forma principale, alcuni nelle caselle di gruppo all'interno del modulo. Voglio che solo l'ultimo controllo selezionato cambi il colore dello sfondo in verde, per il quale ho prima impostato tutti gli altri su bianco, usando questo metodo: (può anche espandersi ai nipoti)

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

Questo non funziona se il controllo figlio ha figli propri.
soulblazer

0

Puoi provare questo se vuoi :)

    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
La semplice pubblicazione di codice fa ben poco per aiutare l'OP a capire il loro problema o la tua soluzione. Dovresti quasi SEMPRE includere una sorta di spiegazione per accompagnare il tuo codice.
leigero

La domanda non diceva nulla sulla cancellazione del modulo.
LarsTech

Sì, non risponde "alla domanda", ma è una bella aggiunta. Grazie!

0

Sebbene molti altri utenti abbiano pubblicato soluzioni adeguate, vorrei pubblicare un approccio più generale che potrebbe essere più utile.

Questo è in gran parte basato sulla risposta di 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

Per chiunque cerchi una versione VB del codice C # di Adam scritto come estensione della 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

NOTA: ho aggiunto la BaseTypecorrispondenza per tutti i controlli personalizzati derivati. Puoi rimuoverlo o persino renderlo un parametro opzionale, se lo desideri.

uso

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

0

Crea metodo

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

E usalo come

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

0

Sto saldy usando VB così, ho scritto un metodo di estensione. Che recupera tutti gli elementi figlio e secondari di un controllo

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

Quindi puoi usarlo come, dove "btnList" è un controllo

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

In questo caso, selezionerà il pulsante di opzione selezionato.


-1

Semplicemente:

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

Questo troverà solo i controlli direttamente nella raccolta di controlli di "Me" e non troverà i controlli Button che si trovano all'interno di qualsiasi contenitore figlio come il poster stava cercando di indicare con "ALL".
ChrisPBacon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.