Цикл по всем текстовым полям в форме, в том числе внутри группового поля

У меня есть несколько текстовых полей в winform, некоторые из них находятся внутри групповой коробки. Я попытался перебрать все текстовые поля в моей форме:

For Each c As Control In Me.Controls
    If c.GetType Is GetType(TextBox) Then
        ' Do something
    End If
Next

Но казалось, что они пропускаются внутри группового блока и зацикливаются только над другими текстовыми полями формы. Поэтому я добавил еще один цикл For Each для текстовых полей группы:

For Each c As Control In GroupBox1.Controls
    If c.GetType Is GetType(TextBox) Then
        ' Do something
    End If
Next

Интересно: есть ли способ перебрать все текстовые поля в форме, в том числе внутри группового блока, с помощью одного цикла For Each? Или какой-нибудь лучший / более элегантный способ сделать это?

Заранее спасибо.

4 ответа

Решение

Вы можете использовать эту функцию, linq может быть более элегантным способом.

Dim allTxt As New List(Of Control)
For Each txt As TextBox In FindControlRecursive(allTxt, Me, GetType(TextBox))
   '....'
Next

Public Shared Function FindControlRecursive(ByVal list As List(Of Control), ByVal parent As Control, ByVal ctrlType As System.Type) As List(Of Control)
    If parent Is Nothing Then Return list
    If parent.GetType Is ctrlType Then
        list.Add(parent)
    End If
    For Each child As Control In parent.Controls
        FindControlRecursive(list, child, ctrlType)
    Next
    Return list
End Function

Вы хотели бы сделать, например, рекурсию(псевдокод, так как я не знаю VB):

Sub LoopControls (Control Container)
    if (Container has Controls)
        LoopControls(Container)
    For Each c As Control In Container
        if (Control is TextBox)
            // do stuff
End Sub

Сначала вы передадите свою форму (мне) в сабвуфер, и она будет проходить по элементам управления в ней, ища те, которые содержат больше элементов управления.

Также проверьте этот вопрос: VB.NET - перебор элементов управления в объекте контейнера

Если вас не волнует порядок (и я не могу представить причину, по которой вы должны) элементов управления, вы можете сделать это итеративно следующим образом (это довольно просто, поэтому я не думаю, что какое-либо объяснение необходимо). Должно быть намного эффективнее любой рекурсии, особенно если у вас много вложенных элементов управления, хотя я сомневаюсь, что прирост производительности будет очевиден.

Public Function FindAllControlsIterative(ByRef parent As Control) As List(Of TextBox)
    Dim list As New List(Of TextBox)
    Dim ContainerStack As New Stack(Of Control)
    ContainerStack.Push(parent)
    While ContainerStack.Count > 0
        For Each child As Control In ContainerStack.Pop().Controls
            If child.HasChildren Then ContainerStack.Push(child)
            If child.GetType Is GetType(TextBox) Then list.Add(child)
        Next
    End While
    Return list
End Function

Вам нужно будет использовать рекурсию. Ниже приведено решение C# с использованием метода расширения, которое выходит за рамки вашего вопроса, но я просто извлек его из нашей среды.

static partial class ControlExtensions
{
    public static void ApplyToMatchingChild(this Control parent, Action<Control> actionToApplyWhenFound, bool keepApplyingForever, params Func<Control, bool>[] matchingChildCriteria)
    {
        ControlEventHandler reapplyEventHandler = null;
        if (keepApplyingForever)
        {
            reapplyEventHandler = (s, e) =>
            {
                ApplyToMatchingChild(e.Control, actionToApplyWhenFound, keepApplyingForever, matchingChildCriteria);
            };
        }
        SearchForMatchingChildTypes(parent, actionToApplyWhenFound, reapplyEventHandler, matchingChildCriteria);
    }

    private static void SearchForMatchingChildTypes(Control control, Action<Control> actionToApplyWhenFound, ControlEventHandler reapplyEventHandler, params Func<Control, bool>[] matchingChildCriteria)
    {
        if (matchingChildCriteria.Any(criteria => criteria(control)))
        {
            actionToApplyWhenFound(control);
        }

        if (reapplyEventHandler != null)
        {
            control.ControlAdded += reapplyEventHandler;
        }

        if (control.HasChildren)
        {
            foreach (var ctl in control.Controls.Cast<Control>())
            {
                SearchForMatchingChildTypes(ctl, actionToApplyWhenFound, reapplyEventHandler, matchingChildCriteria);
            }
        }
    }
}

И позвонить:

myControl.ApplyToMatchingChild(c => { /* Do Stuff to c */ return; }, false, c => c is TextBox);

Это будет применять функцию ко всем дочерним текстовым полям. Вы можете использовать keepApplyingForever Параметр, обеспечивающий применение вашей функции при последующем добавлении дочерних элементов управления. Функция также позволит вам указать любое количество подходящих критериев, например, если элемент управления также является меткой или каким-либо другим критерием.

На самом деле мы используем это как удобный способ вызова нашего контейнера внедрения зависимостей для каждого UserControl, добавленного в нашу форму.

Я уверен, что у вас не должно быть особых проблем с преобразованием его в VB.NET. Кроме того, если вы не хотите использовать функцию "keepApplyingForever", то это тоже должно быть достаточно просто.

Другие вопросы по тегам