web-dev-qa-db-fra.com

Trouver tous les contrôles dans la fenêtre WPF par type

Je cherche un moyen de trouver tous les contrôles sur Window par leur type,

par exemple: trouve tout TextBoxes, trouve tous les contrôles implémentant une interface spécifique, etc.

209
Andrija

Cela devrait faire l'affaire

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

alors vous énumérer sur les contrôles comme si

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}
411
Bryce Kahle

C'est le moyen le plus simple:

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

où control est l'élément racine de la fenêtre.

62
Joel

J'ai adapté la réponse de @Bryce Kahle pour suivre la suggestion de @Mathias Lykkegaard Lorenzen et utiliser LogicalTreeHelper.

Semble travailler bien. ;)

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

(Il ne vérifie toujours pas les contrôles des onglets ou les grilles à l'intérieur des boîtes de groupe comme mentionné par @Benjamin Berry et @David R respectivement.)

21
Simon F

Utilisez les classes d’aide VisualTreeHelper ou LogicalTreeHelper en fonction de arbre vous êtes intéressé. Elles fournissent toutes deux des méthodes pour obtenir les enfants d’un élément (bien que la syntaxe diffère légèrement ). J'utilise souvent ces classes pour trouver la première occurrence d'un type spécifique, mais vous pouvez facilement la modifier pour rechercher tous les objets de ce type:

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

J'ai constaté que la ligne VisualTreeHelper.GetChildrenCount (depObj) ;, utilisée dans plusieurs exemples ci-dessus ne renvoie pas un nombre non nul pour GroupBox, en particulier lorsque GroupBox contient une grille et que la grille contient des éléments enfants. Je pense que cela peut être dû au fait que la GroupBox n'est pas autorisée à contenir plus d'un enfant et que cela est stocké dans sa propriété Content. Il n'y a pas de type de propriété GroupBox.Children. Je suis sûr que je ne l'ai pas fait très efficacement, mais j'ai modifié le premier exemple "FindVisualChildren" dans cette chaîne comme suit:

    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; 
                } 
            }
        }
    } 
9
David R

Petit changement à la récursion afin que vous puissiez par exemple trouver le contrôle de tabulation enfant d'un contrôle de tabulation.

    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;
    }
4
Benjamin Berry

Pour obtenir une liste de tous les enfants d'un type spécifique, vous pouvez utiliser:

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
Michael

Voici encore une autre version compacte, avec la syntaxe générique:

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

Et voici comment ça marche vers le haut

    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
Andreas

Notez que l'utilisation de VisualTreeHelper ne fonctionne que sur les contrôles dérivés de Visual ou Visual3D. Si vous devez également inspecter d'autres éléments (par exemple, TextBlock, FlowDocument, etc.), l'utilisation de VisualTreeHelper lève une exception.

Voici une alternative qui retombe dans l’arbre logique si nécessaire:

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

2
Philipp

Le réponse acceptée renvoie les éléments découverts plus ou moins non numéroté, en suivant la première branche enfant aussi profondément que possible, tout en donnant les éléments découverts en cours de route, avant de revenir en arrière et de répéter les étapes pour les branches d’arbres non encore analysées.

Si vous avez besoin des éléments descendants en ordre décroissant, où les enfants directs seront cédés en premier, puis leurs enfants, etc., l'algorithme suivant fonctionnera:

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

Les éléments résultants seront commandés du plus proche au plus éloigné. Cela sera utile, par exemple. si vous recherchez l'élément enfant le plus proche d'un type ou d'une condition quelconque:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);
1
lauxjpn

Pour une raison quelconque, aucune des réponses affichées ici ne m'a aidé à obtenir tous les contrôles d'un type donné contenus dans un contrôle donné dans ma fenêtre principale. Je devais trouver tous les éléments de menu dans un menu pour les parcourir. Ils n'étaient pas tous des descendants directs du menu, alors j'ai réussi à ne collecter que le premier lilne en utilisant l'un des codes ci-dessus. Cette méthode d’extension est ma solution au problème pour tous ceux qui continueront à lire jusqu’ici.

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

J'espère que ça aide.

1
αNerd

Je voulais ajouter un commentaire, mais comme j’ai moins de 50 points, je ne peux que "Répondre". Sachez que si vous utilisez la méthode "VisualTreeHelper" pour récupérer les objets XAML "TextBlock", il récupérera également les objets XAML "Button". Si vous réinitialisez l'objet "TextBlock" en écrivant dans le paramètre Textblock.Text, vous ne pourrez plus modifier le texte du bouton à l'aide du paramètre Button.Content. Le bouton affichera de manière permanente le texte qui lui est écrit à partir de l'action d'écriture Textblock.Text (à partir du moment où il a été récupéré -

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

Pour contourner ce problème, vous pouvez utiliser une "zone de texte" XAML et ajouter des méthodes (ou événements) pour imiter un bouton XAMAL. XAML "TextBox" n'est pas recueilli par une recherche de "TextBlock".

1
Lifygen

Ma version pour 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
Whiso

Vraiment bonne réponse.

Version 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

Utilisation (cela désactive toutes les zones de texte d'une fenêtre):

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