Як отримати ВСІ дочірні елементи керування формами Windows Forms певного типу (кнопка / текстове поле)?


120

Мені потрібно отримати всі елементи управління на формі, яка має тип x. Я впевнений, що колись бачив цей код, який використовував щось подібне:

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

Я знаю, що можу перебрати всі елементи управління дітьми, які використовують рекурсивну функцію, але чи є щось легше чи простіше, можливо, як наступне?

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

1
Пов'язані питання: stackoverflow.com/questions/253937 / ...
JYelton

Відповіді:


232

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

І кожен раз повертався належним рахунком, тому я думаю, що це буде чудово працювати для того, що ви шукаєте :)


21
GetAll (), визначений тут, є дуже хорошим кандидатом на метод розширення до класу Control
Майкл Бахіг,

Мені сподобалося те, як ви використовували лямбда-вирази. Де детально вивчити лямбдаські вирази?
Адітя Бокаде

"" System.Windows.Forms.Control.ControlCollection "не містить визначення для" Cast "і не може бути знайдено методу розширення" Cast ", який приймає перший аргумент типу" System.Windows.Forms.Control.ControlCollection "(є у вас відсутня директива з використанням або посилання на збірку?) "Я в .NET 4.5 і" Controls "не має функції / методу" Cast "/ що завгодно. Що я пропускаю?
соулблейзер

2
@soulblazer Додати простір імен System.Linq.
Іван-Марк Дебоно

var allCtl = GetAll (this.FindForm (), typeof (TextBox)); // це Usercontrol не повертає нічого !!
bh_earth0

33

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

Дякую, C # або VB мені добре. Але проблеми полягають у тому, що Controls.OfType <TExtbox> повертає лише дітей поточного керування (у моєму випадку Форму), і я хочу за один виклик отримати ВСІ елементи керування у формі "рекурсивно" (діти, піддітки) , під-піддітей, .....) у колекції asingle.
Луїс

Я б очікував, що метод під назвою GetAllControls поверне колекцію елементів управління, яку я призначу ControlList. Просто здається кращою практикою.
ПрофК

@ProfK Я згоден з вами; відповідно змінюючи приклад.
JYelton

13

Це вдосконалена версія рекурсивного 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>());
    }

10

Я поєднав купу попередніх ідей в один метод розширення. Переваги тут полягають у тому, що ви отримуєте правильно введені перелічені назад, плюс успадкування обробляється правильно 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

Для цього можна використовувати запит LINQ. Це буде запитувати все у формі, що вводить TextBox

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

Дякую, але така ж проблема, як і у відповіді, вона повертає лише дітлахи, але не підробочі тощо. Я майже впевнений, що бачив, що це можливо за допомогою єдиного виклику методу, який є новим у .NET 3.5 або 4.0, пам’ятайте, я бачив, що в демонстрації somewehre
Луїс

Ігнорування відсутності рекурсії не var c = this.Controls.OfType<TextBox>()дало б такого ж результату?
CoderDennis

2
@Dennis: Так, так, це питання переваги (як правило). Дивіться stackoverflow.com/questions/214500/…, щоб отримати цікаву дискусію з цього питання.
JYelton

5

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

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

Я хотів би внести зміни до відповіді 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());
}

це найкраще (і найшвидше за моїми тестами) рішення на мою думку на цій сторінці. Але я б запропонував вам змінити елементи управління на масив: var enumerable = управління як Control [] ?? controls.ToArray (); а потім змініть на: return enumerable.SelectMany (FindControls <T>) .Concat (enumerable) .Where (c => c.GetType () == typeof (T)). Cast <T> ();
Рендалл Флагг

Хіба не ефективніше використовувати .OfType<T>()метод Linq, ніж .Where(c => c.GetType() == typeof(T)).Cast<T>();отримувати той же ефект?
TheHitchenator

3

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

Використання рефлексії:

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

Ось мій метод розширення для 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

Чисте і просте рішення (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>();

2

Ви можете використовувати наведений нижче Кодекс

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

Ось мій метод розширення. Це дуже ефективно і ледаче.

Використання:

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

це більш чистий варіант, який ледачий, його можна перераховувати та вибирати на вимогу.
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;

Лямбда-вирази

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

Додайте більше до своєї відповіді, яка пояснює, що відбувається і як це пов’язано з питанням.
Fencer04

0

Я змінив з @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);
}

0

Це може працювати:

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 .


0

Ось перевірене та працююче загальне рішення:

У мене є велика кількість елементів управління 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

Ви можете спробувати це, якщо хочете :)

    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

Питання нічого не сказало про очищення форми.
LarsTech

Так, не відповідає на "запитання", але це приємне доповнення до нього. Дякую!

0

Хоча кілька інших користувачів опублікували відповідні рішення, я хотів би опублікувати більш загальний підхід, який може бути кориснішим.

Це багато в чому ґрунтується на реакції Джейлтона.

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

Для всіх, хто шукає VB-версію коду C # Адама, написану як розширення 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)()

0

Створити метод

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

І використовувати його Like

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

0

Я солоний за допомогою 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)

У цьому випадку він вибере обрану перемикач.


-1

Просто:

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

Це знайде елементи керування безпосередньо у колекції контрольних елементів "Мене", а не знайти контрольні кнопки, які знаходяться в будь-яких дочірніх контейнерах, як плакат намагався зрозуміти "ВСІ".
ChrisPBacon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.