Как получить ВСЕ дочерние элементы управления формы Windows Forms определенного типа (кнопка / текстовое поле)? - PullRequest
107 голосов
/ 06 августа 2010

Мне нужно получить все элементы управления в форме типа x. Я почти уверен, что когда-то видел этот код, который использовал что-то вроде этого:

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

Я знаю, что могу перебрать все элементы управления, получая детей с помощью рекурсивной функции, но Есть ли что-то более простое или более простое, например, следующее?

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

Ответы [ 23 ]

206 голосов
/ 06 августа 2010

Вот еще один вариант для вас. Я протестировал его, создав образец приложения, затем поместил 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());
}

И каждый раз он возвращал правильное количество, так что я думаю, что это будет прекрасно работать для того, что вы ищете

30 голосов
/ 06 августа 2010

В 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;
}
13 голосов
/ 29 июля 2011

Это улучшенная версия рекурсивного 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>());
    }
8 голосов
/ 15 октября 2012

Я объединил несколько предыдущих идей в один метод расширения. Преимущества здесь в том, что вы получаете правильно набираемый перечислимый обратно, а наследование корректно обрабатывается 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)));
}
7 голосов
/ 06 августа 2010

Вы можете использовать запрос LINQ для этого.Это будет запрашивать все в форме, которая имеет тип TextBox

var c = from controls in this.Controls.OfType<TextBox>()
              select controls;
5 голосов
/ 04 апреля 2012

Это может быть древняя техника, но она работает как шарм.Я использовал рекурсию, чтобы изменить цвет всех меток элемента управления.Отлично работает.

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 голосов
/ 08 мая 2014

Я бы хотел изменить ответ 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());
}
3 голосов
/ 16 августа 2011

Не забывайте, что вы также можете иметь 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

Попробуйте это с одной кнопкой и одним 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
2 голосов
/ 16 августа 2016

Вот мой метод расширения для Control, использующий LINQ, в качестве адаптации @ PsychoCoder версия:

Вместо этого требуется список типов, который позволяет вам не использовать несколькозвонки 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;
}
2 голосов
/ 22 июня 2016

Использование отражения:

// 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);
...