Перебирайте все элементы управления в форме, даже в групповых ящиках

Я хочу добавить событие ко всем текстовым полям в моей форме:

foreach (Control C in this.Controls)
{
    if (C.GetType() == typeof(System.Windows.Forms.TextBox))
    {
        C.TextChanged += new EventHandler(C_TextChanged);
    }
}

Проблема в том, что они хранятся в нескольких групповых ящиках, а мой цикл их не видит. Я мог бы управлять элементами управления каждого группового ящика индивидуально, но можно ли все это сделать простым способом в одном цикле?

Ответ 1

Коллекция элементов Controls форм и контейнеров содержит только непосредственные дочерние элементы. Чтобы получить все элементы управления, вам нужно пройти дерево управления и применить эту операцию рекурсивно

private void AddTextChangedHandler(Control parent)
{
    foreach (Control c in parent.Controls)
    {
        if (c.GetType() == typeof(TextBox)) {
            c.TextChanged += new EventHandler(C_TextChanged);
        } else {
            AddTextChangedHandler(c);
        }
    }
}

Примечание. Форма также выводится (косвенно) из Control, а все элементы управления имеют коллекцию Controls. Таким образом, вы можете вызвать метод следующим образом:

AddTextChangedHandler(this);

Более общим решением было бы создать метод расширения, который рекурсивно применяет действие ко всем элементам управления. В статическом классе (например, WinFormsExtensions) добавьте этот метод:

public static void ForAllControls(this Control parent, Action<Control> action)
{
    foreach (Control c in parent.Controls) {
        action(c);
        ForAllControls(c, action);
    }
}

Пространство имен статических классов должно быть "видимым", т.е. добавить соответствующее объявление using, если оно находится в другом пространстве имен.

Затем вы можете вызвать его так, где this - форма; вы также можете заменить this на форму или контрольную переменную, вложенные элементы которой должны быть затронуты:

this.ForAllControls(c =>
{
    if (c.GetType() == typeof(TextBox)) {
        c.TextChanged += C_TextChanged;
    }
});

Ответ 2

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

public static IEnumerable<Control> GetAllChildren(this Control root)
{
    var stack = new Stack<Control>();
    stack.Push(root);

    while (stack.Any())
    {
        var next = stack.Pop();
        foreach (Control child in next.Controls)
            stack.Push(child);
        yield return next;
    }
}

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

foreach(var textbox in GetAllChildren().OfType<Textbox>())
    textbox.TextChanged += C_TextChanged;

Ответ 3

Попробуйте это

AllSubControls(this).OfType<TextBox>().ToList()
    .ForEach(o => o.TextChanged += C_TextChanged);

где AllSubControls

private static IEnumerable<Control> AllSubControls(Control control)
    => Enumerable.Repeat(control, 1)
       .Union(control.Controls.OfType<Control>()
                              .SelectMany(AllSubControls)
             );

LINQ отлично!

Ответ 4

Не видел никого, использующего linq и/или урока, так вот:

public static class UtilitiesX {

    public static IEnumerable<Control> GetEntireControlsTree(this Control rootControl)
    {
        yield return rootControl;
        foreach (var childControl in rootControl.Controls.Cast<Control>().SelectMany(x => x.GetEntireControlsTree()))
        {
            yield return childControl;
        }
    }

    public static void ForEach<T>(this IEnumerable<T> en, Action<T> action)
    {
        foreach (var obj in en) action(obj);
    }
}

Затем вы можете использовать его в своем сердце:

someControl.GetEntireControlsTree().OfType<TextBox>().ForEach(x => x.Click += someHandler);

Ответ 5

Как вы уже сказали, вам нужно идти глубже, чем просто ездить на велосипеде по каждому элементу в вашей форме. Это, к сожалению, подразумевает использование вложенного цикла.

В первом цикле цикл через каждый элемент. ЕСЛИ элемент имеет тип GroupBox, тогда вы знаете, что вам нужно будет циклически перебирать каждый элемент внутри группового ящика, прежде чем продолжить; иначе добавьте событие как обычно.

У вас, похоже, есть приличное понимание С#, поэтому я не дам вам никакого кода; исключительно для того, чтобы вы разработали все важные концепции, которые участвуют в решении проблем:)

Ответ 6

вы можете только прокручивать открытые формы в виде окон, используя коллекцию форм, например, чтобы установить начальную позицию окна для всех открытых форм:

public static void setStartPosition()
        {
            FormCollection fc = Application.OpenForms;

            foreach(Form f in fc)
            {
                f.StartPosition = FormStartPosition.CenterScreen;
            }
        }

Ответ 7

Я знаю, что это более старая тема, но, скажем, фрагмент кода из http://backstreet.ch/coding/code-snippets/mit-c-rekursiv-durch-form-controls-loopen/ является умным решением этой проблемы.

Он использует метод расширения для ControlCollection.

public static void ApplyToAll<T>(this Control.ControlCollection controlCollection, string tagFilter, Action action)
{
    foreach (Control control in controlCollection)
    {
        if (!string.IsNullOrEmpty(tagFilter))
        {
            if (control.Tag == null)
            {
                control.Tag = "";
            }

            if (!string.IsNullOrEmpty(tagFilter) && control.Tag.ToString() == tagFilter && control is T)
            {
                action(control);
            }
        }
        else
        {
            if (control is T)
            {
                action(control);
            }
        }

        if (control.Controls != null && control.Controls.Count > 0)
        {
            ApplyToAll(control.Controls, tagFilter, action);
        }
    }
}

Теперь, чтобы назначить событие всем элементам управления TextBox, вы можете написать инструкцию типа (где 'this' - это форма):

this.Controls.ApplyToAll<TextBox>("", control =>
{
    control.TextChanged += SomeEvent
});

По желанию вы можете фильтровать элементы управления своими тегами.