Найти все элементы управления в окне WPF по типу

218

Я ищу способ найти все элементы управления в Window по их типу,

например: найти все TextBoxes, найти все элементы управления, реализующие определенный интерфейс и т. д.

Андрия
источник
пока мы находимся на этой теме, это также относится к goo.gl/i9RVx
Андрия
Я также написал сообщение в блоге на тему: « Изменение шаблона ControlTemplate во время выполнения»
Адольфо Перес,

Ответы:

430

Это должно сделать свое дело

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            if (child != null && child is T)
            {
                yield return (T)child;
            }

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

затем вы перечисляете элементы управления, как

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}
Брайс Кале
источник
68
Примечание. Если вы пытаетесь заставить это работать, и обнаруживаете, что в вашем окне (например) 0 визуальных потомков, попробуйте запустить этот метод в обработчике событий Loaded. Если вы запустите его в конструкторе (даже после InitializeComponent ()), визуальные дочерние элементы еще не загружены и не будут работать.
Райан Ланди
24
Переключение с VisualTreeHelper на LogicalTreeHelpers также приведет к включению невидимых элементов.
Матиас Ликкегор Лоренцен
11
Разве строка "child! = Null && child is T" избыточна? Если не просто прочитать «ребенок - это Т»
полдень и
1
Я бы превратил это в метод расширения, просто вставив thisbefore DependencyObject=>this DependencyObject depObj
Johannes Wanzek
1
@JohannesWanzek Не забывайте, что вам также нужно изменить бит, где вы его называете для ребенка: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
будет
66

Это самый простой способ:

IEnumerable<myType> collection = control.Children.OfType<myType>(); 

где control - корневой элемент окна.

Joel
источник
1
что вы имеете в виду "корневой элемент"? Что я должен написать, чтобы соединиться с моей формой главного окна?
Deadfish
Я понял, в представлении xaml мне нужно было задать имя для сетки, <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>а затем я мог бы использоватьAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish
68
Это не отвечает на вопрос, который был задан. Он возвращает только дочерние элементы управления на один уровень глубиной.
Джим
21

Я адаптировал ответ @Bryce Kahle, чтобы следовать предложению и использованию @Mathias Lykkegaard Lorenzen LogicalTreeHelper.

Кажется, работает хорошо. ;)

public static IEnumerable<T> FindLogicalChildren<T> ( DependencyObject depObj ) where T : DependencyObject
{
    if( depObj != null )
    {
        foreach( object rawChild in LogicalTreeHelper.GetChildren( depObj ) )
        {
            if( rawChild is DependencyObject )
            {
                DependencyObject child = (DependencyObject)rawChild;
                if( child is T )
                {
                    yield return (T)child;
                }

                foreach( T childOfChild in FindLogicalChildren<T>( child ) ) 
                {
                    yield return childOfChild;
                }
            }
        }
    }
}

(Он по-прежнему не будет проверять элементы управления вкладками или сетки внутри групповых ячеек, как упомянуто @Benjamin Berry и @David R соответственно.) (Также следовал совету @ noonand и удалил лишнего дочернего элемента! = Null)

Саймон Ф
источник
некоторое время искал, как очистить все мои текстовые поля, у меня есть несколько вкладок, и это единственный код, который сработал :) спасибо
JohnChris
13

Используйте вспомогательные классы VisualTreeHelperили в LogicalTreeHelperзависимости от того, какое дерево вас интересует. Они оба предоставляют методы для получения дочерних элементов (хотя синтаксис немного отличается). Я часто использую эти классы для поиска первого вхождения определенного типа, но вы можете легко изменить его, чтобы найти все объекты этого типа:

public static DependencyObject FindInVisualTreeDown(DependencyObject obj, Type type)
{
    if (obj != null)
    {
        if (obj.GetType() == type)
        {
            return obj;
        }

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject childReturn = FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type);
            if (childReturn != null)
            {
                return childReturn;
            }
        }
    }

    return null;
}
Oskar
источник
+1 за объяснение и пост, но Брайс Кале разместил функцию, которая полностью работает. Спасибо
Андрия
Это не решает проблему вопроса, а также ответ с универсальным типом намного яснее. Объединение его с использованием VisualTreeHelper.GetChildrenCount (obj) решит проблему. Однако полезно рассматривать как вариант.
Василь Попов
9

Я обнаружил, что строка, VisualTreeHelper.GetChildrenCount(depObj);используемая в нескольких приведенных выше примерах, не возвращает ненулевое число для GroupBoxes, в частности, где элементы содержит элементы GroupBoxa Gridи Gridдочерние элементы. Я полагаю, что это может быть потому, что GroupBoxне может содержать более одного дочернего элемента, и это хранится в его Contentсобственности. Там нет GroupBox.Childrenтипа собственности. Я уверен, что сделал это не очень эффективно, но я изменил первый пример «FindVisualChildren» в этой цепочке следующим образом:

public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject 
{ 
    if (depObj != null) 
    {
        int depObjCount = VisualTreeHelper.GetChildrenCount(depObj); 
        for (int i = 0; i <depObjCount; i++) 
        { 
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i); 
            if (child != null && child is T) 
            { 
                yield return (T)child; 
            }

            if (child is GroupBox)
            {
                GroupBox gb = child as GroupBox;
                Object gpchild = gb.Content;
                if (gpchild is T)
                {
                    yield return (T)child; 
                    child = gpchild as T;
                }
            }

            foreach (T childOfChild in FindVisualChildren<T>(child)) 
            { 
                yield return childOfChild; 
            } 
        }
    }
} 
Дэвид Р
источник
4

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

private static IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, Type type)
{
    if (obj != null)
    {
        if (obj.GetType() == type)
        {
            yield return obj;
        }

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
            {
                if (child != null)
                {
                    yield return child;
                }
            }
        }
    }

    yield break;
}
Майкл
источник
4

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

    public static DependencyObject FindInVisualTreeDown(DependencyObject obj, Type type)
    {
        if (obj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);

                if (child.GetType() == type)
                {
                    return child;
                }

                DependencyObject childReturn = FindInVisualTreeDown(child, type);
                if (childReturn != null)
                {
                    return childReturn;
                }
            }
        }

        return null;
    }
Бенджамин Берри
источник
3

Вот еще одна компактная версия с обобщенным синтаксисом:

    public static IEnumerable<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
    {
        if (obj != null) {
            if (obj is T)
                yield return obj as T;

            foreach (DependencyObject child in LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>()) 
                foreach (T c in FindLogicalChildren<T>(child)) 
                    yield return c;
        }
    }
user1656671
источник
2

И вот как это работает вверх

    private T FindParent<T>(DependencyObject item, Type StopAt) where T : class
    {
        if (item is T)
        {
            return item as T;
        }
        else
        {
            DependencyObject _parent = VisualTreeHelper.GetParent(item);
            if (_parent == null)
            {
                return default(T);
            }
            else
            {
                Type _type = _parent.GetType();
                if (StopAt != null)
                {
                    if ((_type.IsSubclassOf(StopAt) == true) || (_type == StopAt))
                    {
                        return null;
                    }
                }

                if ((_type.IsSubclassOf(typeof(T)) == true) || (_type == typeof(T)))
                {
                    return _parent as T;
                }
                else
                {
                    return FindParent<T>(_parent, StopAt);
                }
            }
        }
    }

источник
2

Обратите внимание, что использование VisualTreeHelper работает только с элементами управления, производными от Visual или Visual3D. Если вам также необходимо проверить другие элементы (например, TextBlock, FlowDocument и т. Д.), Использование VisualTreeHelper вызовет исключение.

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

http://www.hardcodet.net/2009/06/finding-elements-in-wpf-tree-both-ways

Philipp
источник
1

Я хотел добавить комментарий, но у меня меньше 50 баллов, поэтому я могу только «Ответить». Помните, что если вы используете метод «VisualTreeHelper» для извлечения объектов XAML «TextBlock», то он также будет захватывать объекты «кнопки» XAML. Если вы повторно инициализируете объект «TextBlock» путем записи в параметр Textblock.Text, вы больше не сможете изменять текст Button с помощью параметра Button.Content. Кнопка будет постоянно показывать текст, записанный в нее из действия записи Textblock.Text (с момента, когда он был извлечен -

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
// do something with tb here
   tb.Text = ""; //this will overwrite Button.Content and render the 
                 //Button.Content{set} permanently disabled.
}

Чтобы обойти это, вы можете попробовать использовать XAML «TextBox» и добавить методы (или события) для имитации кнопки XAMAL. XAML «TextBox» не собирается поиском «TextBlock».

Lifygen
источник
В этом разница между визуальным и логическим деревом. Визуальное дерево содержит все элементы управления (включая те, из которых сделан элемент управления, которые определены в шаблоне элементов управления), в то время как логическое дерево содержит только фактические элементы управления (без определенных в шаблонах). Здесь есть хорошая визуализация этой концепции: ссылка
lauxjpn
1

Моя версия для C ++ / CLI

template < class T, class U >
bool Isinst(U u) 
{
    return dynamic_cast< T >(u) != nullptr;
}

template <typename T>
    T FindVisualChildByType(Windows::UI::Xaml::DependencyObject^ element, Platform::String^ name)
    {
        if (Isinst<T>(element) && dynamic_cast<Windows::UI::Xaml::FrameworkElement^>(element)->Name == name)
        {
            return dynamic_cast<T>(element);
        }
        int childcount = Windows::UI::Xaml::Media::VisualTreeHelper::GetChildrenCount(element);
        for (int i = 0; i < childcount; ++i)
        {
            auto childElement = FindVisualChildByType<T>(Windows::UI::Xaml::Media::VisualTreeHelper::GetChild(element, i), name);
            if (childElement != nullptr)
            {
                return childElement;
            }
        }
        return nullptr;
    };
Whiso
источник
1

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

public static void FindVisualChildren<T>(this ICollection<T> children, DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            var brethren = LogicalTreeHelper.GetChildren(depObj);
            var brethrenOfType = LogicalTreeHelper.GetChildren(depObj).OfType<T>();
            foreach (var childOfType in brethrenOfType)
            {
                children.Add(childOfType);
            }

            foreach (var rawChild in brethren)
            {
                if (rawChild is DependencyObject)
                {
                    var child = rawChild as DependencyObject;
                    FindVisualChildren<T>(children, child);
                }
            }
        }
    }

Надеюсь, поможет.

αNerd
источник
1

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

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

public static IEnumerable<T> GetVisualDescendants<T>(DependencyObject parent, bool applyTemplates = false)
    where T : DependencyObject
{
    if (parent == null || !(child is Visual || child is Visual3D))
        yield break;

    var descendants = new Queue<DependencyObject>();
    descendants.Enqueue(parent);

    while (descendants.Count > 0)
    {
        var currentDescendant = descendants.Dequeue();

        if (applyTemplates)
            (currentDescendant as FrameworkElement)?.ApplyTemplate();

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(currentDescendant); i++)
        {
            var child = VisualTreeHelper.GetChild(currentDescendant, i);

            if (child is Visual || child is Visual3D)
                descendants.Enqueue(child);

            if (child is T foundObject)
                yield return foundObject;
        }
    }
}

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

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);
lauxjpn
источник
1
Что-то не хватает; childне определено
Codebender
1

@ Брайс, действительно хороший ответ.

Версия VB.NET:

Public Shared Iterator Function FindVisualChildren(Of T As DependencyObject)(depObj As DependencyObject) As IEnumerable(Of T)
    If depObj IsNot Nothing Then
        For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(depObj) - 1
            Dim child As DependencyObject = VisualTreeHelper.GetChild(depObj, i)
            If child IsNot Nothing AndAlso TypeOf child Is T Then
                Yield DirectCast(child, T)
            End If
            For Each childOfChild As T In FindVisualChildren(Of T)(child)
                Yield childOfChild
            Next
        Next
    End If
End Function

Использование (это отключает все текстовые поля в окне):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next
Андреа Антонангели
источник
0

Для этого и других случаев вы можете добавить текущий метод расширения в вашу библиотеку:

 public static List<DependencyObject> FindAllChildren(this DependencyObject dpo, Predicate<DependencyObject> predicate)
    {
        var results = new List<DependencyObject>();
        if (predicate == null)
            return results;


        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dpo); i++)
        {
            var child = VisualTreeHelper.GetChild(dpo, i);
            if (predicate(child))
                results.Add(child);

            var subChildren = child.FindAllChildren(predicate);
            results.AddRange(subChildren);
        }
        return results;
    }

Пример для вашего случая:

 var children = dpObject.FindAllChildren(child => child is TextBox);
Ахмад Камхиех
источник
-1

Мне было проще без Visual Tree Helpers:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};
Рафаэль Вентура
источник
3
Это идет только на один уровень глубиной. в XAML у вас есть глубоко вложенные элементы управления.
SQL Police