Знайдіть усі елементи керування у WPF Window за типом


218

Я шукаю спосіб знайти всі елементи керування у вікні за їх типом,

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


поки ми на тему, це також актуально goo.gl/i9RVx
Андрія

Я також написав допис у блозі на тему: Зміна контрольного шаблону під час виконання
Adolfo Perez

Відповіді:


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 дітей із візуальними ознаками, спробуйте запустити цей метод у завантаженому події обробника подій. Якщо запустити його в конструкторі (навіть після InitializeComponent ()), наочні діти ще не завантажені, і він не працюватиме.
Райан Лунді

24
Перехід з VisualTreeHelper на LogicalTreeHelpers також спричинить включення невидимих ​​елементів.
Mathias Lykkegaard Lorenzen

11
Чи не є зайвим рядок "дитина! = Null && child is T"? Чи слід не просто читати "дитина - Т"
опівдні

1
Я б перетворив це на метод розширення, просто вставивши thisперед DependencyObject= =this DependencyObject depObj
Йоганнес Ванзек

1
@JohannesWanzek Не забувайте , ви також повинні змінити трохи , де ви викликаєте його на дитину: Еогеасп (ChildofChild.FindVisualChildren <T> ()) {бла бла бла}
Буде

66

Це найпростіший спосіб:

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

де контроль є кореневим елементом вікна.


1
що ви маєте на увазі "кореневий елемент"? Що потрібно написати, щоб з'єднатись із формою основного вікна?
мертвий рибок

Я розумію, в xaml view мені довелося встановити ім'я для сітки, <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;
                }
            }
        }
    }
}

(Він все ще не перевірятиме елементи керування на вкладках або сітки всередині GroupBoxes, як згадує @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;
}

+1 для пояснення та повідомлення, але Брайс Кале опублікував функцію, яка повністю працює Дякую
Андрія

Це не виправляє питання питання, а також відповідь із родовим типом набагато чіткіше. Поєднавши його з використанням VisualTreeHelper.GetChildrenCount (obj), виправите проблему. Однак корисно розглянути як варіант.
Василь Попов

9

Я виявив, що рядок, VisualTreeHelper.GetChildrenCount(depObj);використаний у кількох прикладах, наведених вище, не повертає нульовий підрахунок для GroupBoxes, зокрема, де GroupBoxмістить a 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

Ось ще одна, компактна версія, із синтаксисом generics:

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

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


1

Я хотів додати коментар, але у мене менше 50 балів, тому я можу лише "Відповісти". Майте на увазі, що якщо ви використовуєте метод "VisualTreeHelper" для отримання об'єктів XAML "TextBlock", він також захопить об'єкти XAML "Кнопка". Якщо ви повторно ініціалізуєте об’єкт "TextBlock", записавши в параметр Textblock.Text, ви більше не зможете змінити текст кнопки за допомогою параметра 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".


Ось і є різниця між візуальним та логічним деревом. Візуальне дерево містить кожен елемент управління (включаючи ті, з яких складається елемент керування, визначених у шаблоні елементів управління), тоді як логічне дерево містить лише фактичні елементи управління (без тих, що визначені в шаблонах). Тут є приємна візуалізація цієї концепції: посилання
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;
    };

1

Чомусь жоден з наведених тут відповідей не допоміг мені отримати всі елементи керування заданого типу, що містяться в даному елементі управління в моєму MainWindow. Мені потрібно було знайти всі пункти меню в одному меню, щоб повторити їх. Вони не були всіма прямими нащадками меню, тому мені вдалося зібрати лише першу лілію з них, використовуючи будь-який з наведених вище кодів. Цей метод розширення є моїм вирішенням проблеми для всіх, хто продовжить читати тут увесь шлях.

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

Сподіваюся, це допомагає.


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

1
Чогось не вистачає; childне визначено.
codebender

1

@Bryce, дуже приємна відповідь.

Версія 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

Використання (це вимикає всі TextBoxes у вікні):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next

-1

Мені було легше без помічників Visual Tree:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};

3
Це йде лише на один рівень глибоко. у XAML у вас є глибоко вкладені елементи управління.
SQL Police
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.