пятница, 20 января 2012 г.

Расширения для класса FrameworkElement

Разработчики WPF (и Silverlight) меня поймут. Microsoft создала мощную технологию для создания пользовательского интерфейса декларативным образом. Но, к сожалению, не все можно реализовать декларативно. Приходится иногда и код месить. И вот тут ожидает неприятный сюрприз. Концептуально понятная иерархия интерфейсных элементов оказывается недоступной напрямую, а чтобы достучаться до неё приходится обращаться к классу VisualTreeHelper. Это, бесспорно, очень нужный и полезный класс, но использование методов типа GetChildrenCount() и GetChild() в эпоху LINQ несколько раздражает.
Я уже писал о пользе и удобстве расширений, когда рассказывал о расширениях для класса Exception, повторяться не буду. А расскажу о нескольких очень простых расширениях для класса FrameworkElement, которые очень облегчают жизнь в некоторых сценариях.

Получение всех дочерних элементов

Этот метод позволяет рекурсивно получить все дочерние элементы в виде перечисления.
/// <summary>

/// Получить перечисление дочерних визуальных объектов (рекурсивное перечисление)

/// </summary>

static public IEnumerable<FrameworkElement> GetTree(this FrameworkElement e)

{

    if (e == null)

        return new FrameworkElement[] { };

 

    int total = VisualTreeHelper.GetChildrenCount(e) ;

 

    return new FrameworkElement[] { e }

               .Concat(

                    Enumerable.Range(0, total)

                    .SelectMany(x => GetTree(VisualTreeHelper.GetChild(e, x) 

                                as FrameworkElement)))

 

               .Where(x => x != null);

}

Хочу обратить внимание, что перечисление – это Linq выражение, которое вычисляется лениво, т.е. если выполняется перебор не всех элементов, то лишних обращений к методам VisualTreeHelper не происходит.

Вот как теперь выглядит поиск всех дочерних элементов нужного типа:

var boxes = myGryd.GetTree().OfType<CheckBox>().ToList();


Еще раз обращу внимание на то, что поиск выполняется на всю глубину вложенности.

Поиск родителя


Часто возникает задача – найти родительский элемент определенного класса, или который реализует определенный интерфейс. Вот метод, который это делает:


/// <summary>

/// Получить ближайший родительский визуальный объект заданного типа

/// </summary>

static public T GetAncestor<T>(this DependencyObject e) 

                               where T : DependencyObject

{

    if (e == null)

        return null;

 

    var parent = (DependencyObject)VisualTreeHelper.GetParent(e);

 

    /// Идем вверх по родителям, пока не найдем ScrollViewer

    while ((parent != null) && !(parent is T))

    {

        parent = (DependencyObject)VisualTreeHelper.GetParent(parent);

    }

 

    return (T)parent;

}

Применение этого метода очень просто:

var myTree = node.GetAncestor<TreeView>();


Поиск элемента по имени


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



/// <summary>

/// усложненный вариант поиска элемента по имени

/// сначала вызывается стандартный метод

/// если он ничего не вернет, то ищем сами

/// </summary>

public static object FindChildByName(this FrameworkElement e, string name)

{

    return e.FindName(name) ??

           e.GetTree().FirstOrDefault(x => x.Name == name);

}

Думаю, пример тут приводить излишне, и так все понятно :-).

И в заключение


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

Если Вы программируете с использованием WPF, то, возможно, Вам будет интересно:

Комментариев нет:

Отправить комментарий