如何获取特定类型(按钮/文本框)的 Windows 窗体窗体的所有子控件?

How to get ALL child controls of a Windows Forms form of a specific type (Button/Textbox)?

提问人:Luis 提问时间:8/6/2010 最后编辑:Vadim OvchinnikovLuis 更新时间:1/8/2022 访问量:240687

问:

我需要获取类型为 x 的窗体上的所有控件。我很确定我过去曾经看到过使用过这样的代码:

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

我知道我可以使用递归函数遍历所有控件,但是 有没有更简单或更直接的东西,也许像下面这样?

Dim Ctrls = From ctrl In Me.Controls Where ctrl.GetType Is Textbox
C# .NET vb.net WinForms 控件

评论

1赞 JYelton 8/6/2010
相关问题:stackoverflow.com/questions/253937/...
0赞 Olivier Jacot-Descombes 7/5/2021
为此,我提出了一个 API 建议:为 github.com/dotnet/winforms 上的控件添加后代属性。如果您喜欢它,请为它投赞成票。

答:

43赞 JYelton 8/6/2010 #1

在 C# 中(因为您这样标记了它),您可以使用如下所示的 LINQ 表达式:

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

编辑递归:

在此示例中,首先创建控件列表,然后调用方法来填充它。由于该方法是递归的,因此它不会返回列表,而只是更新它。

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

尽管我对它不太熟悉,但可以使用该函数在一个 LINQ 语句中执行此操作。有关这方面的更多信息,请参阅此页面Descendants

编辑 2 以返回集合:

正如@ProfK所建议的,仅返回所需控件的方法可能是更好的做法。为了说明这一点,我修改了代码如下:

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

评论

1赞 Luis 8/6/2010
谢谢,C#或VB对我来说很好。但问题是 Controls.OfType<TExtbox> 只返回当前控件的子控件(在我的情况下是 Form),并且我希望在单个调用中“递归”获取 Forma 中的所有控件(chiilds、sub-childs、sub-sub-childs,.....)在单个集合中。
0赞 ProfK 10/9/2012
我希望一个名为 GetAllControls 的方法返回控件集合,我会将其分配给 ControlList。只是似乎更好的练习。
0赞 JYelton 10/9/2012
@ProfK 我同意你的看法;相应地更改示例。
5赞 PsychoCoder 8/6/2010 #2

可以使用 LINQ 查询来执行此操作。这将查询类型为 TextBox 的窗体上的所有内容

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

评论

0赞 Luis 8/6/2010
谢谢,但与答案相同的问题,它只返回 chidls 而不返回子项等,我想要所有 ensted 控件。我很确定我看到它可以通过 .NET 3.5 或 4.0 中新增的单个方法调用来实现,请记住我在演示中看到了这一点
0赞 CoderDennis 8/7/2010
忽略缺乏递归,不会给出相同的结果吗?var c = this.Controls.OfType<TextBox>()
2赞 JYelton 8/9/2010
@Dennis:是的,这是一个偏好问题(通常)。请参阅 stackoverflow.com/questions/214500/...,了解关于此事的有趣讨论。
0赞 Alex Rouillard 8/6/2010 #3

这可能有效:

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

我认为获取您正在谈论的所有控件的功能仅适用于 WPF

260赞 PsychoCoder 8/7/2010 #4

这是您的另一种选择。我通过创建一个示例应用程序对其进行了测试,然后将 GroupBox 和 GroupBox 放入初始 GroupBox 中。在嵌套的 GroupBox 中,我放置了 3 个 TextBox 控件和一个按钮。这是我使用的代码(甚至包括您正在寻找的递归)

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

为了在表单加载事件中测试它,我想要对初始 GroupBox 内的所有控件进行计数

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

它每次都返回正确的计数,所以我认为这将非常适合您正在寻找的内容:)

评论

25赞 Michael Bahig 2/27/2012
这里定义的 GetAll() 是类 Control 的扩展方法的一个很好的候选者
0赞 Aditya Bokade 7/17/2013
我喜欢你使用 lambda 表达式的方式。在哪里可以详细学习 lambda 表达式?
0赞 soulblazer 5/9/2015
“'System.Windows.Forms.Control.ControlCollection'不包含'Cast'的定义,并且找不到接受'System.Windows.Forms.Control.ControlCollection'类型的第一个参数的扩展方法'Cast'(是否缺少使用指令或程序集引用?我在 .NET 4.5 上,“控件”没有“强制转换”功能/方法/任何东西。我错过了什么?
3赞 Ivan-Mark Debono 5/11/2015
@soulblazer 添加 System.Linq 命名空间。
0赞 bh_earth0 8/22/2017
var allCtl = GetAll(this.FindForm(), typeof(TextBox));这是一个用户控件返回 Nothing!!
14赞 VictorEspina 7/29/2011 #5

这是递归 GetAllControls() 的改进版本,它实际上适用于私有变量:

    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>());
    }
3赞 John Anthony Oliver 8/16/2011 #6

不要忘记,除了容器控件之外,还可以在其他控件中拥有 TextBox。您甚至可以将 TextBox 添加到 PictureBox。

所以你还需要检查一下是否

someControl.HasChildren = True

在任何递归函数中。

这是我从布局中得到的结果,用于测试此代码:

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

在窗体上使用一个 Button 和一个 RichTextBox 进行此操作。

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
5赞 Aditya Bokade 4/4/2012 #7

这可能是古老的技术,但它就像魅力一样。我使用递归来更改控件所有标签的颜色。效果很好。

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;
            }
    }
}
0赞 ted 4/9/2012 #8

我从@PsychoCoder修改。 现在可以找到所有控件(包括嵌套控件)。

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);
}
12赞 Entiat 10/16/2012 #9

我把前面的一堆想法组合成一个扩展方法。这样做的好处是,您可以获得正确键入的枚举对象,并且继承由 正确处理。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)));
}
1赞 DareDevil 10/8/2013 #10

这是解决方案。

https://stackoverflow.com/a/19224936/1147352

我写了这段代码,只选择了面板,可以添加更多的开关或ifs。在其中

0赞 samtal 1/28/2014 #11

下面是一个经过测试且有效的通用解决方案:

我有大量的UpDownNumeric控件,有些在主窗体中,有些在窗体内的分组框中。 我只想让最后一个选定的控件将背景颜色更改为绿色,为此,我首先使用以下方法将所有其他控件设置为白色:(也可以扩展到孙)

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

评论

0赞 soulblazer 5/9/2015
如果子控件有自己的子控件,则此操作不起作用。
4赞 Adam 5/8/2014 #12

我想修改一下 PsychoCoders 的答案:由于用户想要获得某种类型的所有控件,我们可以通过以下方式使用泛型:

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

这样,我们可以按如下方式调用函数:

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

评论

0赞 Randall Flagg 4/28/2015
在我看来,这是本页最好的(也是最快的)解决方案。但我建议您将控件更改为数组:var enumerable = controls as Control[] ??控制。ToArray();然后更改为:return enumerable。选择 Many(FindControls<T>)。Concat(enumerable) (可枚举) 。其中 (c => c.GetType() == typeof(T))。演员<T>();
0赞 IAmJersh 10/24/2018
使用 Linq 方法不是比获得相同的效果更有效吗?.OfType<T>().Where(c => c.GetType() == typeof(T)).Cast<T>();
1赞 Top Systems 5/10/2015 #13
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;
}
0赞 rashi 8/13/2015 #14

如果你愿意,你可以试试这个:)

    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赞 leigero 8/13/2015
简单地发布代码对帮助 OP 理解他们的问题或您的解决方案几乎没有帮助。你几乎总是应该包括某种解释来配合你的代码。
0赞 LarsTech 8/14/2015
这个问题没有提到任何关于清除表格的事情。
0赞 5/27/2016
是的,没有回答“问题”,但是一个很好的补充。谢谢!
0赞 JamesFaix 12/29/2015 #15

尽管其他几位用户已经发布了足够的解决方案,但我想发布一种可能更有用的更通用的方法。

这很大程度上是基于 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;
}
2赞 MiMenda 6/22/2016 #16

使用反射:

// 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赞 gfache 8/16/2016 #17

这是我使用 LINQ 作为@PsychoCoder版本的改编的扩展方法:Control

它采用一个类型的列表,使你不需要多次调用来获得你想要的东西。我目前将其用作重载版本。GetAll

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

用法:

//   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;
}
0赞 Koray 10/11/2016 #18
    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;
        }
    }
1赞 Memo Arfaa 12/6/2016 #19
   IEnumerable<Control> Ctrls = from Control ctrl in Me.Controls where ctrl is TextBox | ctrl is GroupBox select ctr;

Lambda 表达式

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

评论

0赞 Fencer04 12/6/2016
请在您的答案中添加更多内容,以解释正在发生的事情以及它与问题的关系。
0赞 Valeh Mikayilzadeh 2/1/2017 #20
    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;
    }
4赞 Omar 3/8/2017 #21

简洁易用的解决方案 (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;
    }
}

获取所有文本框:

List<TextBox> textboxes = myControl.GetAllControls<TextBox>();
3赞 Jone Polvora 7/2/2017 #22

这是我的扩展方法。它非常高效,而且很懒惰。

用法:

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

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

代码为:

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

评论

0赞 Jone Polvora 3/23/2020
这是一个更简洁的版本,是懒惰的,可以按需枚举和获取。
0赞 SteveCinq 12/29/2017 #23

对于任何寻找作为类扩展编写的 Adam C# 代码的 VB 版本的人:Control

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

注意:我为任何派生的自定义控件添加了匹配。如果您愿意,您可以将其删除,甚至可以将其设为可选参数。BaseType

用法

Dim oButtons As IEnumerable(Of Button) = Me.GetControls(Of Button)()
3赞 Santanu Sarkar 8/25/2019 #24

您可以使用以下代码

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>());
    }
}
-1赞 Leebeedev 8/30/2019 #25

只是:

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

评论

0赞 ChrisPBacon 2/4/2020
这将仅直接在“Me”的控件集合中查找控件,而不会找到位于任何子容器中的 Button 控件,因为海报试图通过“ALL”暗示。
1赞 Lucifer 3/19/2020 #26

Create 方法

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

并像

Var controls= GetControlsOfType<TextBox>(this);//You can replace this with your control
1赞 Iannick 5/22/2020 #27

我很喜欢使用VB,所以,我写了一个扩展方法。检索控件的所有子项和子项

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

然后你可以像这样使用它,其中“btnList”是一个控件

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

在这种情况下,它将选择选定的单选按钮。

0赞 fcm 1/8/2022 #28

VISUAL BASIC VB.NET对于我们中的一些人来说,他们拒绝将 230,000+ 行代码移植到 c#,这是我的贡献,如果只需要一个特定的类型,只需根据需要添加一个“where”。

Private Shared Function getAll(control As Control) As IEnumerable(Of Control)
    Return control.Controls.Cast(Of Control) _
        .SelectMany(Function(f) getAll(f).Concat(control.Controls.Cast(Of Control)))
End Function