Znajdź wszystkie elementy sterujące w oknie WPF według typu

218

Szukam sposobu na znalezienie wszystkich elementów sterujących w systemie Windows według ich typu,

na przykład: znajdź wszystko TextBoxes, znajdź wszystkie elementy sterujące implementujące określony interfejs itp.

Andrija
źródło
gdy jesteśmy w temacie, dotyczy to również goo.gl/i9RVx
Andrija
Napisałem również post na blogu na ten temat: Modyfikowanie elementu ControlTemplate w środowisku wykonawczym
Adolfo Perez

Odpowiedzi:

430

To powinno załatwić sprawę

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

następnie wyliczyć w ten sposób elementy sterujące

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}
Bryce Kahle
źródło
68
Uwaga: jeśli próbujesz uruchomić to i odkryjesz, że twoje okno (na przykład) ma 0 elementów potomnych, spróbuj uruchomić tę metodę w module obsługi zdarzeń Loaded. Jeśli uruchomisz go w konstruktorze (nawet po InitializeComponent ()), wizualne elementy potomne nie zostaną jeszcze załadowane i nie będzie działać.
Ryan Lundy,
24
Przejście z VisualTreeHelper na LogicalTreeHelpers spowoduje włączenie również elementów niewidocznych.
Mathias Lykkegaard Lorenzen
11
Czy linia „child! = Null && child is T” nie jest zbędna? Czy to nie tylko napis „dziecko to T”
południe i
1
Zamieniłbym to w metodę rozszerzenia po prostu wstawiając thisprzed DependencyObject=>this DependencyObject depObj
Johannes Wanzek
1
@JohannesWanzek Nie zapominaj, że musiałbyś również zmienić bit, w którym nazywasz go dzieckiem: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
Czy
66

To najprostszy sposób:

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

gdzie kontrola jest głównym elementem okna.

Joel
źródło
1
co masz na myśli „element główny”? Co mam napisać, aby połączyć się z formularzem głównego okna?
deadfish
Rozumiem, w widoku Xaml musiałem ustawić nazwę dla <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>Anata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
grida,
68
To nie odpowiada na zadane pytanie. Zwraca kontrolę dziecka tylko o jeden poziom głębokości.
Jim
21

Dostosowałem odpowiedź @Bryce Kahle, aby postępować zgodnie z sugestią @Mathiasa Lykkegaarda Lorenzena LogicalTreeHelper.

Wydaje się, że działa dobrze. ;)

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

(Nadal nie sprawdza kontrolek tabulatorów ani siatek w GroupBoxach, jak wspomniano odpowiednio przez @Benjamin Berry i @David R.). (Również zastosował się do sugestii @ noonand i usunął zbędne dziecko! = Null)

Simon F.
źródło
szukałem przez jakiś czas, jak wyczyścić wszystkie pola tekstowe, mam wiele zakładek i to jest jedyny kod, który zadziałał :) dzięki
JohnChris
13

Skorzystaj z klas pomocników VisualTreeHelperlub w LogicalTreeHelperzależności od drzewa, które Cię interesuje. Obie zapewniają metody uzyskiwania elementów potomnych elementu (chociaż składnia jest nieco inna). Często używam tych klas do znalezienia pierwszego wystąpienia określonego typu, ale można go łatwo zmodyfikować, aby znaleźć wszystkie obiekty tego typu:

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
źródło
+1 za wyjaśnienia i posty, ale Bryce Kahle opublikował funkcję, która w pełni działa Dzięki
Andrija
To nie rozwiązuje problemu pytania, a także odpowiedź z typem ogólnym jest znacznie jaśniejsza. Połączenie go z użyciem VisualTreeHelper.GetChildrenCount (obj) naprawi problem. Jednak warto rozważyć tę opcję.
Vasil Popov
9

Odkryłem, że linia VisualTreeHelper.GetChildrenCount(depObj);użyta w kilku powyższych przykładach nie zwraca niezerowej liczby dla GroupBoxes, w szczególności tam, gdzie GroupBoxzawiera a Grid, i Gridzawiera elementy potomne. Wierzę, że może tak być, ponieważ GroupBoxnie może zawierać więcej niż jednego dziecka i jest to przechowywane w jego Contentwłaściwości. Nie ma żadnego GroupBox.Childrenrodzaju nieruchomości. Jestem pewien, że nie zrobiłem tego bardzo skutecznie, ale zmodyfikowałem pierwszy przykład „FindVisualChildren” w tym łańcuchu w następujący sposób:

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; 
            } 
        }
    }
} 
David R.
źródło
4

Aby uzyskać listę wszystkich dzieci określonego typu, możesz użyć:

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;
}
Michael
źródło
4

Niewielka zmiana w rekursji do, abyś mógł na przykład znaleźć kontrolkę tabulatora potomnego kontrolki tabulatora.

    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;
    }
Benjamin Berry
źródło
3

Oto kolejna kompaktowa wersja z ogólną składnią:

    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;
        }
    }
użytkownik1656671
źródło
2

I tak to działa w górę

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

źródło
2

Zauważ, że użycie VisualTreeHelper działa tylko na kontrolkach pochodzących z Visual lub Visual3D. Jeśli musisz również sprawdzić inne elementy (np. TextBlock, FlowDocument itp.), Użycie VisualTreeHelper spowoduje zgłoszenie wyjątku.

Oto alternatywa, która w razie potrzeby wraca do drzewa logicznego:

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

Philipp
źródło
1

Chciałem dodać komentarz, ale mam mniej niż 50 pkt, więc mogę tylko „odpowiedzieć”. Należy pamiętać, że jeśli użyjesz metody „VisualTreeHelper” do pobrania obiektów XAML „TextBlock”, to również pobierze obiekty XAML „Button”. Jeśli ponownie zainicjujesz obiekt „TextBlock”, pisząc do parametru Textblock.Text, nie będziesz już mógł zmieniać tekstu przycisku za pomocą parametru Button.Content. Przycisk na stałe pokaże tekst zapisany do niego z Textblocka. Operacja zapisu tekstu (od momentu jej pobrania -

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

Aby obejść ten problem, możesz spróbować użyć „TextBox” XAML i dodać metody (lub zdarzenia) naśladujące przycisk XAMAL. XAML „TextBox” nie jest gromadzony przez wyszukiwanie „TextBlock”.

Lifygen
źródło
Na tym polega różnica między drzewem wizualnym a logicznym. Drzewo wizualne zawiera wszystkie elementy sterujące (w tym te, z których składa się element sterujący, które są zdefiniowane w szablonie elementów sterujących), natomiast drzewo logiczne zawiera tylko elementy sterujące rzeczywiste (bez elementów zdefiniowanych w szablonach). Ładna wizualizacja tej koncepcji tutaj: link
lauxjpn
1

Moja wersja dla 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
źródło
1

Z jakiegoś powodu żadna z zamieszczonych tutaj odpowiedzi nie pomogła mi uzyskać wszystkich kontrolek danego typu zawartych w danej kontrolce w moim MainWindow. Musiałem znaleźć wszystkie elementy menu w jednym menu, aby je powtórzyć. Nie wszyscy byli bezpośrednimi potomkami menu, więc udało mi się zebrać tylko pierwszą z nich, używając dowolnego z powyższych kodów. Ta metoda rozszerzenia jest moim rozwiązaniem problemu dla każdego, kto będzie dalej czytał tutaj.

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

Mam nadzieję, że to pomoże.

αNerd
źródło
1

Odpowiedź Zaakceptowany Zwraca odkryte elementy mniej lub bardziej nieuporządkowana , wykonując pierwszy oddział dziecko jak najgłębiej, a plonowanie odkrytych elementów po drodze, przed backtracking i powtarzając kroki jeszcze analizowanych gałęziach drzew.

Jeśli potrzebujesz elementów potomnych w kolejności malejącej , w których pierwszymi potomkami będą pierwsze, a następnie ich dzieci itd., Zadziała następujący algorytm:

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

Powstałe elementy zostaną uporządkowane od najbliższego do najdalszego. Będzie to przydatne np. Jeśli szukasz najbliższego elementu potomnego określonego typu i warunku:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);
lauxjpn
źródło
1
Czegoś brakuje; childjest niezdefiniowany.
autor kodu
1

@Bryce, naprawdę fajna odpowiedź.

Wersja 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

Użycie (wyłącza wszystkie TextBoxy w oknie):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next
Andrea Antonangeli
źródło
-1

Łatwiej mi było bez Visual Tree Helpers:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};
Rafael Ventura
źródło
3
To sięga tylko jeden poziom. w XAML masz głęboko zagnieżdżone kontrolki.
SQL Police