Нахождение управления на Winforms с помощью LINQ?

Я пытаюсь найти изящный способ получить средства управления на форме Windows Forms по имени. Например:

MyForm.GetControl "MyTextBox"

...

Но это должно удостовериться, что проходит все средства управления рекурсивно.

Что самый изящный путь состоит в том, чтобы реализовать это использование LINQ?

8
задан SomeRandomPeople 18 August 2016 в 05:06
поделиться

4 ответа

LINQ не обязательно лучше подходит для неизвестной глубинной рекурсии; просто используйте обычный код...

public static Control FindControl(this Control root, string name) {
    if(root == null) throw new ArgumentNullException("root");
    foreach(Control child in root.Controls) {
        if(child.Name == name) return child;
        Control found = FindControl(child, name);
        if(found != null) return found;
    }
    return null;
}

с:

Control c = myForm.GetControl("MyTextBox");

Или если вам не нравится рекурсия выше:

public Control FindControl(Control root, string name) {
    if (root == null) throw new ArgumentNullException("root");
    var stack = new Stack<Control>();
    stack.Push(root);
    while (stack.Count > 0) {
        Control item = stack.Pop();
        if (item.Name == name) return item;
        foreach (Control child in item.Controls) {
            stack.Push(child);
        }
    }
    return null;
}
8
ответ дан 5 December 2019 в 05:34
поделиться

"Элегантный" управляющий фильтр (без LINQ)

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

Он фильтрует все элементы управления для заданных критериев (может фильтровать по нескольким критериям). Возвращает несколько совпадений. Позволяет не только определять имя.

    /// <summary>
    /// Recurses through all controls, starting at given control,
    /// and returns an array of those matching the given criteria.
    /// </summary>

    public Control[] FilterControls(Control start, Func<Control, bool> isMatch) {
        var matches = new List<Control>();

        Action<Control> filter = null;
        (filter = new Action<Control>(c => {
            if (isMatch(c))
                matches.Add(c);
            foreach (Control c2 in c.Controls)
                filter(c2);
        }))(start);

        return matches.ToArray();
    }

Фильтр Использует...

Оно достаточно гибкое, так как использование

Control[] foundControls = null;

// Find control with Name="tabs1".
foundControls = FilterControls(this,
    c => c.Name != null && c.Name.Equals("tabs1"));

// Find all controls that start with ID="panel*...
foundControls = FilterControls(this,
    c => c.Name != null && c.Name.StartsWith("panel"));

// Find all Tab Pages in this form.
foundControls = FilterControls(this,
    c => c is TabPage);

Console.Write(foundControls.Length); // is an empty array if no matches found.

Эквивалентного метода расширения

Методы расширения также добавляют наследника элегантности приложениям.

Точно такая же логика может быть инжектирована в метод расширения.

static public class ControlExtensions {

    static public Control[] FilterControls(this Control start, Func<Control, bool> isMatch) {
        // Put same logic here as seen above (copy & paste)
    }
}

Расширение использование является:

// Find control with Name="tabs1" in the Panel.
panel1.FilterControls(c => c.Name != null && c.Name.Equals("tabs1"));

// Find all panels in this form
this.FilterControls(c => c is Panel);

Другое расширение для возврата одного элемента управления или null

Вызов первого метода расширения (см. выше) для получения всех элементов управления совпадением, затем возвращает первый в совпадениях, в противном случае null, если список совпадений пуст.

Это не эффективно, так как итерация происходит над всеми элементами управления даже после нахождения первого совпадения - но просто играется здесь ради SO-комментариев.

    static public Control FilterControlsOne(this Control start, Func<Control, bool> isMatch) {
        Control[] arrMatches = ControlExtensions.FilterControls(start, isMatch);
        return arrMatches.Length == 0 ? null : arrMatches[0];
    }
14
ответ дан 5 December 2019 в 05:34
поделиться

Не так просто...

LINQ не очень хорошо работает с рекурссией и Control.Controls не включен LINQ (нужно Cast).

Иногда метод является лучшим решением. Так как вы можете написать метод, который работает для всех элементов управления, то он будет даже более многоразовым, чем LINQ-запрос. Это может быть метод расширения.

1
ответ дан 5 December 2019 в 05:34
поделиться

Я не думаю, что вы можете создавать рекурсивные запросы linq напрямую, но вы можете создать рекурсивный метод с помощью linq:

public IEnumerable<Control> FlattenHierarchy(this Control c)
{
    return new[] { c }.Concat(c.Controls.Cast<Control>().SelectMany(child => child.FlattenHierarchy()));
}

Он должен возвращать последовательность, содержащую все элементы управления, содержащиеся в иерархии элементов управления. Тогда поиск совпадения будет простым:

public Control FindInHierarchy(this Control control, string controlName)
{
    return control.FlattenHierarchy().FirstOrDefault(c => c.Name == controlName);
}

Лично я бы избегал использования linq таким образом.

7
ответ дан 5 December 2019 в 05:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: