Найти элемент управления на Winforms, используя LINQ - PullRequest
8 голосов
/ 08 января 2010

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

MyForm.GetControl "MyTextBox"

...

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

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

Ответы [ 4 ]

14 голосов
/ 08 января 2010

«Элегантный» контрольный фильтр (без 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

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

Это неэффективно, потому что оно перебирает все элементы управления даже после нахождения первого совпадения - но просто играет здесь ради 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];
    }
8 голосов
/ 08 января 2010

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;
}
7 голосов
/ 08 января 2010

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

1 голос
/ 08 января 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...