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

218

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

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

Andrija
la source
alors que nous sommes sur le sujet, c'est aussi pertinent goo.gl/i9RVx
Andrija
J'ai également écrit un article de blog sur le sujet: Modification d'un ControlTemplate à l'exécution
Adolfo Perez

Réponses:

430

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érez les contrôles comme ça

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}
Bryce Kahle
la source
68
Remarque: Si vous essayez de faire fonctionner cela et que vous constatez que votre fenêtre (par exemple) a 0 enfants visuels, essayez d'exécuter cette méthode dans le gestionnaire d'événements Loaded. Si vous l'exécutez dans le constructeur (même après InitializeComponent ()), les enfants visuels ne sont pas encore chargés et cela ne fonctionnera pas.
Ryan Lundy
24
Le passage de VisualTreeHelper à LogicalTreeHelpers entraînera également l'inclusion d'éléments invisibles.
Mathias Lykkegaard Lorenzen
11
La ligne "child! = Null && child is T" n'est-elle pas redondante? Faut-il simplement lire "l'enfant est T"
midi et
1
Je le transformerais en une méthode d'extension en insérant juste un thisavant DependencyObject=>this DependencyObject depObj
Johannes Wanzek
1
@JohannesWanzek N'oubliez pas que vous devrez également changer le bit où vous l'appelez sur l'enfant: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
Will
66

C'est le moyen le plus simple:

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

où contrôle est l'élément racine de la fenêtre.

Joel
la source
1
que voulez-vous dire par "élément racine"? Que dois-je écrire pour me connecter à mon formulaire mainwindow?
deadfish
Je comprends, en vue xaml, j'ai dû définir le nom de la grille <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>, puis je pourrais utiliserAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish
68
Cela ne répond pas à la question posée. Il ne renvoie que les contrôles enfants d'un niveau en profondeur.
Jim
21

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

Semble fonctionner correctement. ;)

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 d'onglet ou les grilles à l'intérieur des GroupBox comme mentionné respectivement par @Benjamin Berry et @David R.) (A également suivi la suggestion de @ noonand et supprimé l'enfant redondant! = Null)

Simon F
la source
cherche depuis un certain temps comment effacer toutes mes zones de texte, j'ai plusieurs onglets et c'est le seul code qui a fonctionné :) merci
JohnChris
13

Utilisez les classes d'assistance VisualTreeHelperou LogicalTreeHelperselon l' arborescence qui vous intéresse. Elles fournissent toutes deux des méthodes pour obtenir les enfants d'un élément (bien que la syntaxe diffère un peu). J'utilise souvent ces classes pour trouver la première occurrence d'un type spécifique, mais vous pouvez facilement le modifier pour trouver 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;
}
Oskar
la source
+1 pour explication et publication, mais Bryce Kahle a publié une fonction qui fonctionne pleinement Merci
Andrija
Cela ne résout pas le problème de la question, et la réponse avec le type générique est également beaucoup plus claire. Le combiner avec l'utilisation de VisualTreeHelper.GetChildrenCount (obj) résoudra le problème. Il est cependant utile d'être considéré comme une option.
Vasil Popov
9

J'ai trouvé que la ligne, VisualTreeHelper.GetChildrenCount(depObj);utilisée dans plusieurs exemples ci-dessus ne retourne pas un nombre non nul pour GroupBoxes, en particulier, où GroupBoxcontient un Gridet Gridcontient les éléments enfants. Je crois que cela peut être dû au fait que le GroupBoxn'est pas autorisé à contenir plus d'un enfant, et cela est stocké dans sa Contentpropriété. Il n'y a aucun GroupBox.Childrentype de bien. Je suis sûr que je n'ai pas fait cela très efficacement, mais j'ai modifié le premier exemple "FindVisualChildren" de 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; 
            } 
        }
    }
} 
David R
la source
4

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;
}
Michael
la source
4

Petite modification de la récursivité pour que vous puissiez par exemple trouver le contrôle onglet enfant d'un contrôle onglet.

    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
la source
3

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;
        }
    }
user1656671
la source
2

Et c'est comme ça que ça marche

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

la source
2

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

Voici une alternative qui revient à l'arborescence logique si nécessaire:

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

Philipp
la source
1

Je voulais ajouter un commentaire mais j'ai moins de 50 points donc je ne peux que "Répondre". Sachez que si vous utilisez la méthode "VisualTreeHelper" pour récupérer des objets XAML "TextBlock", il récupérera également des 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 en permanence 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 essayer d'utiliser un "TextBox" XAML et ajouter des méthodes (ou événements) pour imiter un bouton XAMAL. XAML "TextBox" n'est pas collecté par une recherche de "TextBlock".

Lifygen
la source
C'est la différence entre le visuel et l'arbre logique. L'arborescence visuelle contient tous les contrôles (y compris ceux dont un contrôle est fait, qui sont définis dans le modèle de contrôles) tandis que l'arborescence logique contient uniquement les contrôles réels (sans ceux définis dans les modèles). Il y a une belle visualisation de ce concept ici: lien
lauxjpn
1

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;
    };
Whiso
la source
1

Pour une raison quelconque, aucune des réponses publiées ici ne m'a aidé à obtenir tous les contrôles de type donné contenus dans un contrôle donné dans ma fenêtre principale. J'avais besoin de trouver tous les éléments de menu dans un seul menu pour les répéter. Ils n'étaient pas tous des descendants directs du menu, j'ai donc réussi à ne collecter que la première lilne d'entre eux 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 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.

αNerd
la source
1

La réponse acceptée renvoie les éléments découverts plus ou moins désordonnés , en suivant la première branche enfant aussi profondément que possible, tout en produisant 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'arbre non encore analysées.

Si vous avez besoin des éléments descendants dans l'ordre décroissant , où les enfants directs seront générés en premier, puis leurs enfants et ainsi de suite, 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 classé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 et d'une condition:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);
lauxjpn
la source
1
Il manque quelque chose; childn'est pas défini.
codebender
1

@Bryce, réponse vraiment sympa.

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 tous les TextBox dans une fenêtre):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next
Andrea Antonangeli
la source
-1

Je l'ai trouvé plus facile sans Visual Tree Helpers:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};
Rafael Ventura
la source
3
Cela ne va que d'un niveau en profondeur. en XAML, vous avez des contrôles profondément imbriqués.
SQL Police