Como obter todos os controles filho de um formulário do Windows Forms de um tipo específico (botão / caixa de texto)?


120

Eu preciso obter todos os controles em um formulário que são do tipo x. Tenho certeza de que vi esse código uma vez no passado que usava algo como isto:

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

Eu sei que posso iterar todos os controles, recebendo crianças usando uma função recursiva, mas há algo mais fácil ou mais direto, talvez o seguinte?

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

Respostas:


232

Aqui está outra opção para você. Testei-o criando um aplicativo de exemplo e depois coloquei um GroupBox e um GroupBox dentro do GroupBox inicial. Dentro do GroupBox aninhado, coloquei 3 controles TextBox e um botão. Este é o código que eu usei (inclui até a recursão que você estava procurando)

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 testá-lo no evento load do formulário, eu queria uma contagem de todos os controles dentro do GroupBox inicial

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

E ele retornou a contagem adequada a cada vez, então acho que isso funcionará perfeitamente para o que você está procurando :)


21
GetAll () definido aqui é um bom candidato para um método de extensão para a classe de Controle
Michael Bahig

Gostei da maneira como você usou expressões lambda. Onde aprender expressões lambda em detalhes?
Aditya Bokade

"'System.Windows.Forms.Control.ControlCollection' não contém uma definição para 'Cast' e nenhum método de extensão 'Cast' que aceita um primeiro argumento do tipo 'System.Windows.Forms.Control.ControlCollection' pode ser encontrado (são falta uma diretiva de uso ou uma referência de montagem?) "Estou no .NET 4.5 e" Controls "não tem nenhuma função" Cast "/ método / qualquer coisa. o que estou perdendo?
Soulblazer # 8/15

2
@soulblazer Adicione o espaço para nome System.Linq.
Ivan-Mark Debono

var allCtl = GetAll (this.FindForm (), typeof (TextBox)); // este é um Usercontrol retorna Nothing !!
precisa saber é o seguinte

33

Em C # (desde que você o marcou como tal), você pode usar uma expressão LINQ como esta:

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

Edite para recursão:

Neste exemplo, você primeiro cria a lista de controles e depois chama um método para preenchê-lo. Como o método é recursivo, ele não retorna a lista, apenas a atualiza.

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

Pode ser possível fazer isso em uma instrução LINQ usando a Descendantsfunção, embora eu não esteja tão familiarizado com ela. Veja esta página para mais informações.

Edite 2 para retornar uma coleção:

Como o @ProfK sugeriu, um método que simplesmente retorna os controles desejados é provavelmente a melhor prática. Para ilustrar isso, modifiquei o código da seguinte maneira:

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

Obrigado, C # ou VB é bom para mim. Mas o problema é que Controls.OfType <TExtbox> retorna apenas os filhos do controle atual (no meu caso, o Form), e desejo em uma única chamada obter TODOS os controles no Forma "recursivamente" (crianças, sub-filhos) , sub-sub-filhos, .....) em uma única coleção.
Luis

Eu esperaria que um método chamado GetAllControls retornasse uma coleção de controles, que eu atribuiria ao ControlList. Apenas parece uma prática melhor.
ProfK 9/10/12

@ProfK Eu concordo com você; mudando exemplo de acordo.
JYelton

13

Esta é uma versão aprimorada do GetAllControls () recursivo que realmente funciona em vars particulares:

    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

Combinei várias idéias anteriores em um método de extensão. Os benefícios aqui são que você recebe o enumerável digitado corretamente de volta, e a herança é manipulada corretamente por 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

Você pode usar uma consulta LINQ para fazer isso. Isso consultará tudo no formulário que é do tipo TextBox

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

Obrigado, mas o mesmo problema que a resposta lá, ele retorna apenas os chidls, mas não os sub-filhos, etc., e eu quero todos os controles garantidos. Eu tenho certeza que eu vi que é posible com uma única chamada de método que é nova no .NET 3.5 ou 4.0, lembre-se que eu vi que em um somewehre demonstração
Luis

Ignorar a falta de recursão, não var c = this.Controls.OfType<TextBox>()daria o mesmo resultado?
CoderDennis

2
@ Dennis: Sim, seria uma questão de preferência (geralmente). Veja stackoverflow.com/questions/214500/… para uma discussão interessante sobre o assunto.
precisa saber é o seguinte

5

Pode ser a técnica antiga, mas funciona como charme. Usei a recursão para alterar a cor de todos os rótulos do controle. Isso funciona muito bem.

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

Gostaria de alterar a resposta do PsychoCoders: como o usuário deseja obter todos os controles de um determinado tipo, podemos usar genéricos da seguinte maneira:

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

Dessa forma, podemos chamar a função da seguinte maneira:

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

esta é a melhor (e mais rápida de acordo com meus testes) na minha opinião nesta página. Mas eu sugiro que você altere os controles para uma matriz: var enumerable = controls como Control [] ?? controls.ToArray (); e mude para: return enumerable.SelectMany (FindControls <T>) .Concat (enumerable) .Where (c => c.GetType () == typeof (T)). Cast <T> ();
Randall Flagg

Não é mais eficiente usar o .OfType<T>()método Linq do que .Where(c => c.GetType() == typeof(T)).Cast<T>();obter o mesmo efeito?
TheHitchenator 24/10/19

3

Não esqueça que você também pode ter um TextBox dentro de outros controles que não sejam controles de contêiner. Você pode até adicionar um TextBox a um PictureBox.

Então você também precisa verificar se

someControl.HasChildren = True

em qualquer função recursiva.

Este é o resultado que tive de um layout para testar 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

Tente isso com um botão e um RichTextBox em um formulário.

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

Usando reflexão:

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

Aqui está o meu método de extensão Control, usando o LINQ, como uma adaptação da versão @PsychoCoder :

É preciso uma lista do tipo que permite que você não precise de várias chamadas GetAllpara obter o que deseja. Atualmente, eu o uso como uma versão 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;
}

2

Uma solução limpa e 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;
    }
}

Obtenha todas as caixas de texto:

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

2

Você pode usar o código abaixo

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

Aqui está o meu método de extensão. É muito eficiente e preguiçoso.

Uso:

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

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

O código é:

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

Esta é uma versão mais limpa, que é preguiçosa, pode ser enumerada e buscada sob demanda.
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;

Expressões Lambda

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

Adicione mais à sua resposta, que explica o que está acontecendo e como está relacionada à pergunta.
precisa saber é o seguinte

0

Eu modifiquei do @PsychoCoder. Todos os controles podem ser encontrados agora (incluem aninhados).

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

Isso pode 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

Eu acho que a função para obter todos os controles dos quais você está falando está disponível apenas para o WPF .


0

Aqui está uma solução genérica testada e funcionando:

Eu tenho um grande número de controles UpDownNumeric, alguns no formulário principal, outros em caixas de grupo no formulário. Desejo que apenas o último controle selecionado mude a cor de volta para verde, para o qual primeiro defino todos os outros para branco, usando este método: (também pode ser expandido para netos)

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

Isso não funciona se o controle filho tiver filhos próprios.
Soulblazer # 8/15

0

Você pode tentar isso se quiser :)

    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
A simples publicação do código faz pouco para ajudar o OP a entender o problema ou a sua solução. Você quase sempre deve incluir algum tipo de explicação para acompanhar seu código.
Leigero 13/08/2015

A pergunta não disse nada sobre como limpar o formulário.
LarsTech

Sim, não responde "à pergunta", mas é uma boa adição. Obrigado!

0

Embora vários outros usuários tenham postado soluções adequadas, gostaria de postar uma abordagem mais geral que possa ser mais útil.

Isso se baseia amplamente na resposta 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

Para quem procura uma versão VB do código C # de Adam, escrita como uma extensão da 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: eu adicionei BaseType correspondência para qualquer controle personalizado derivado. Você pode remover isso ou até torná-lo um parâmetro opcional, se desejar.

Uso

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

0

Método de Criação

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 use-o como

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

0

Como estou usando o VB, escrevi um método de extensão. Que recupera todos os filhos e sub filhos de um controle

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

Então você pode usá-lo como, onde "btnList" é um controle

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

Nesse caso, ele selecionará o botão de opção selecionado.


-1

Simplesmente:

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

Isso encontrará apenas controles diretamente na coleção de controles "Eu" e não encontrará controles de Botão que estejam dentro de qualquer contêiner filho, como o pôster estava tentando sugerir com "TUDO".
ChrisPBacon
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.