Поиск элемента управления на Winforms с помощью LINQ?

Я пытаюсь найти элегантный способ, чтобы получить контроль над Windows Forms формы по имени. Например:

MyForm.GetControl "MyTextBox"

...

но это, чтобы убедиться, что он проходит через все элементы управления рекурсивно.

какой самый элегантный способ реализовать это с помощью LINQ?

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

" элегантный " фильтр управления (без 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];
    }

Я не думаю, что вы можете создавать рекурсивные запросы 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 таким образом.


Не так легко...

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

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