¿Cómo obtener TODOS los controles secundarios de un formulario de Windows Forms de un tipo específico (botón / cuadro de texto)?

120

Necesito obtener todos los controles en un formulario que sea de tipo x. Estoy bastante seguro de que vi ese código una vez en el pasado que usaba algo como esto:

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

Sé que puedo iterar sobre todos los controles haciendo que los niños usen una función recursiva, pero ¿hay algo más fácil o más sencillo, tal vez como lo siguiente?

Dim Ctrls = From ctrl In Me.Controls Where ctrl.GetType Is Textbox
Luis
fuente
1
Pregunta relacionada: stackoverflow.com/questions/253937/…
JYelton

Respuestas:

232

Aquí tienes otra opción. Lo probé creando una aplicación de muestra, luego puse un GroupBox y un GroupBox dentro del GroupBox inicial. Dentro del GroupBox anidado puse 3 controles TextBox y un botón. Este es el código que usé (incluso incluye la recursividad que estabas buscando)

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

Para probarlo en el evento de carga de formulario, quería un recuento de todos los controles dentro del GroupBox inicial

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

Y devolvió el recuento adecuado cada vez, así que creo que esto funcionará perfectamente para lo que está buscando :)

PsychoCoder
fuente
21
GetAll () definido aquí es un muy buen candidato para un método de extensión para la clase Control
Michael Bahig
Me gustó la forma en que usaste las expresiones lambda. ¿Dónde aprender las expresiones lambda en detalle?
Aditya Bokade
"'System.Windows.Forms.Control.ControlCollection' no contiene una definición para 'Cast' y ningún método de extensión 'Cast' aceptando un primer argumento de tipo 'System.Windows.Forms.Control.ControlCollection' podría ser encontrado (son ¿Te falta una directiva using o una referencia de ensamblado?) "Estoy en .NET 4.5 y" Controls "no tiene función / método" Cast "/ lo que sea. ¿Qué me estoy perdiendo?
soulblazer
2
@soulblazer Agregar espacio de nombres System.Linq.
Ivan-Mark Debono
var allCtl = GetAll (this.FindForm (), typeof (TextBox)); // esto es un Usercontrol devuelve Nothing !!
bh_earth0
33

En C # (ya que lo etiquetó como tal) podría usar una expresión LINQ como esta:

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

Editar para recursividad:

En este ejemplo, primero crea la lista de controles y luego llama a un método para completarla. Dado que el método es recursivo, no devuelve la lista, solo la actualiza.

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

Puede ser posible hacer esto en una declaración LINQ usando la Descendantsfunción, aunque no estoy tan familiarizado con ella. Consulte esta página para obtener más información al respecto.

Edite 2 para devolver una colección:

Como sugirió @ProfK, un método que simplemente devuelve los controles deseados probablemente sea una mejor práctica. Para ilustrar esto, modifiqué el código de la siguiente manera:

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;
}
JYelton
fuente
Gracias, C # o VB están bien para mí. Pero el problema es que Controls.OfType <TExtbox> solo devuelve los elementos secundarios del control actual (en mi caso el Formulario), y quiero en una sola llamada obtener TODOS los controles en la Forma "recursivamente" (chiilds, sub-childs , sub-sub-childs, .....) en una sola colección.
Luis
Esperaría que un método llamado GetAllControls devuelva una colección de controles, que asignaría a ControlList. Parece una mejor práctica.
ProfK
@ProfK Estoy de acuerdo contigo; cambiando el ejemplo en consecuencia.
JYelton
13

Esta es una versión mejorada del recursivo GetAllControls () que realmente funciona en variables privadas:

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

Combiné un montón de ideas anteriores en un método de extensión. Los beneficios aquí son que obtiene el enumerable correctamente escrito, además de que la herencia se maneja correctamente 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)));
}
Entiat
fuente
5

Puede utilizar una consulta LINQ para hacer esto. Esto consultará todo en el formulario que sea de tipo TextBox

var c = from controls in this.Controls.OfType<TextBox>()
              select controls;
PsychoCoder
fuente
Gracias, pero el mismo problema que la respuesta, solo devuelve los chidls pero no los subchilds, etc., y quiero todos los controles instalados. Estoy bastante seguro de que vi que es posible con una sola llamada a un método que es nuevo en .NET 3.5 o 4.0, recuerde que lo vi en una demostración en algún sitio
Luis
Ignorando la falta de recursividad, ¿no var c = this.Controls.OfType<TextBox>()daría el mismo resultado?
CoderDennis
2
@Dennis: Sí, es una cuestión de preferencia (normalmente). Consulte stackoverflow.com/questions/214500/… para ver una discusión interesante sobre el tema.
JYelton
5

Puede que sea la técnica antigua, pero funciona de maravilla. Usé la recursividad para cambiar el color de todas las etiquetas del control. Funciona muy 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;
            }
    }
}
Aditya Bokade
fuente
4

Me gustaría modificar la respuesta de PsychoCoders: como el usuario desea obtener todos los controles de un cierto tipo, podríamos usar genéricos de la siguiente manera:

    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 esta forma, podemos llamar a la función de la siguiente manera:

private void Form1_Load(object sender, EventArgs e)
{
    var c = FindControls<TextBox>(this);
    MessageBox.Show("Total Controls: " + c.Count());
}
Adán
fuente
esta es la mejor (y más rápida según mis pruebas) solución en mi opinión en esta página. Pero le sugiero que cambie los controles a una matriz: var enumerable = controls as Control [] ?? controles.ToArray (); y luego cambie a: return enumerable.SelectMany (FindControls <T>) .Concat (enumerable) .Where (c => c.GetType () == typeof (T)). Cast <T> ();
Randall Flagg
¿No es más eficiente utilizar el .OfType<T>()método Linq que .Where(c => c.GetType() == typeof(T)).Cast<T>();obtener el mismo efecto?
TheHitchenator
3

No olvide que también puede tener un TextBox dentro de otros controles además de los controles de contenedor. Incluso puede agregar un TextBox a un PictureBox.

Por lo tanto, también debe verificar si

someControl.HasChildren = True

en cualquier función recursiva.

Este es el resultado que obtuve de un diseño para probar este código:

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

Pruebe esto con un botón y un RichTextBox en un formulario.

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
John Anthony Oliver
fuente
2

Usando la reflexión:

// 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);
MiMenda
fuente
2

Aquí está mi método de extensión para Control, usando LINQ, como una adaptación de la versión de @PsychoCoder :

En su lugar, se necesita una lista de tipos que le permite no necesitar múltiples llamadas GetAllpara obtener lo que desea. Actualmente lo uso como una versión de sobrecarga.

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;
}
gfache
fuente
2

Una solución limpia y fácil (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;
    }
}

Obtén todos los cuadros de texto:

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

Puede utilizar el siguiente código

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>());
    }
}
Santanu Sarkar
fuente
2

Aquí está mi método de extensión. Es muy eficiente y perezoso.

Uso:

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

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

El codigo es:

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;
                }
            }
        }
    }
Jone Polvora
fuente
esta es una versión más limpia que es perezosa, se puede enumerar y buscar a pedido.
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;
}
Sistemas superiores
fuente
1
   IEnumerable<Control> Ctrls = from Control ctrl in Me.Controls where ctrl is TextBox | ctrl is GroupBox select ctr;

Expresiones lambda

IEnumerable<Control> Ctrls = Me.Controls.Cast<Control>().Where(c => c is Button | c is GroupBox);
Memo Arfaa
fuente
Agregue más a su respuesta que explique qué está sucediendo y cómo se relaciona con la pregunta.
Fencer04
0

Modifiqué de @PsychoCoder. Todos los controles se pueden encontrar ahora (incluidos los anidados).

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);
}
ted
fuente
0

Esto puede funcionar:

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

Creo que la función para obtener todos los controles de los que estás hablando solo está disponible para WPF .

Alex Rouillard
fuente
0

Aquí hay una solución genérica probada y funcional:

Tengo una gran cantidad de controles UpDownNumeric, algunos en el formulario principal, algunos en cuadros de grupo dentro del formulario. Quiero que solo el último control seleccionado cambie el color de fondo a verde, para lo cual primero configuro todos los demás en blanco, usando este método: (también se puede expandir a nietos)

    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;
       }
    }   
samtal
fuente
Esto no funciona si el control de hijos tiene hijos propios.
soulblazer
0

Puedes probar esto si quieres :)

    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);
    }
rashi
fuente
1
La simple publicación de código hace poco para ayudar al OP a comprender su problema o su solución. Casi SIEMPRE debería incluir algún tipo de explicación para acompañar su código.
leigero
La pregunta no decía nada sobre limpiar el formulario.
LarsTech
Sí, no responde "la pregunta", pero es una buena adición. ¡Gracias!
0

Aunque varios otros usuarios han publicado soluciones adecuadas, me gustaría publicar un enfoque más general que puede ser más útil.

Esto se basa en gran medida en la respuesta 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;
}
JamesFaix
fuente
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;
        }
    }
Koray
fuente
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;
    }
Valeh Mikayilzadeh
fuente
0

Para cualquiera que busque una versión VB del código C # de Adam escrito como una extensión de la Controlclase:

''' <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: He agregado BaseTypecoincidencias para cualquier control personalizado derivado. Puede eliminar esto o incluso convertirlo en un parámetro opcional si lo desea.

Uso

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

Método de creación

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

Y usarlo como

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

Estoy saldy usando VB, así que escribí un método de extensión. Que recuperan todos los hijos y sub hijos de un control

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

Entonces puedes usarlo como, donde "btnList" es un control

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

En este caso, seleccionará el botón de radio seleccionado.

Iannick
fuente
-1

Simplemente:

For Each ctrl In Me.Controls.OfType(Of Button)()
   ctrl.Text = "Hello World!"
Next
Leebeedev
fuente
Esto solo encontrará controles directamente en la colección de controles de "Yo" y no encontrará controles de botón que estén dentro de los contenedores secundarios, como el cartel estaba tratando de dar a entender con "TODOS".
ChrisPBacon