Trova tutti i controlli nella finestra di WPF per tipo


Risposte:


430

Questo dovrebbe fare il trucco

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

quindi si enumera sui controlli in questo modo

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

68
Nota: se stai cercando di farlo funzionare e scopri che la tua finestra (ad esempio) ha 0 figli visivi, prova a eseguire questo metodo nel gestore di eventi Loaded. Se lo esegui nel costruttore (anche dopo InitializeComponent ()), i figli visivi non sono ancora caricati e non funzioneranno.
Ryan Lundy,

24
Il passaggio da VisualTreeHelper a LogicalTreeHelpers comporterà anche l'inclusione di elementi invisibili.
Mathias Lykkegaard Lorenzen il

11
La riga "child! = Null && child is T" non è ridondante? Dovrebbe non solo leggere "bambino è T"
mezzogiorno e

1
Lo trasformerei in un metodo di estensione semplicemente inserendo un thisbefore DependencyObject=>this DependencyObject depObj
Johannes Wanzek,

1
@JohannesWanzek Non dimenticare che avresti anche bisogno di cambiare il bit in cui lo chiami sul bambino: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
Will

66

Questo è il modo più semplice:

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

dove il controllo è l'elemento radice della finestra.


1
cosa intendi per "elemento radice"? Cosa devo scrivere per connettermi con il mio modulo mainwindow?
pesce morto

Ho capito, in vista xaml ho dovuto impostare il nome per la griglia <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>e quindi ho potuto usareAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish

68
Questo non risponde alla domanda che è stata posta. Restituisce solo i controlli figlio a un livello di profondità.
Jim,

21

Ho adattato la risposta di @Bryce Kahle per seguire il suggerimento e l'uso di @Mathias Lykkegaard Lorenzen LogicalTreeHelper.

Sembra funzionare bene. ;)

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

(Non controlla ancora i controlli di tabulazione o Griglie all'interno di GroupBox come menzionato rispettivamente da @Benjamin Berry e @David R.) (Ha anche seguito il suggerimento di @ noonand e rimosso il bambino ridondante! = Null)


sto cercando da un po 'come deselezionare tutte le mie caselle di testo, ho più schede e questo è l'unico codice che ha funzionato :) grazie
JohnChris,

13

Usa le classi helper VisualTreeHelpero LogicalTreeHelperdipende dall'albero che ti interessa. Entrambi forniscono metodi per ottenere i figli di un elemento (sebbene la sintassi differisca leggermente). Uso spesso queste classi per trovare la prima occorrenza di un tipo specifico, ma potresti facilmente modificarlo per trovare tutti gli oggetti di quel tipo:

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 per spiegazione e post, ma Bryce Kahle ha pubblicato una funzione che funziona perfettamente Grazie
Andrija

Ciò non risolve il problema della domanda e anche la risposta con il tipo generico è molto più chiara. Combinandolo con l'uso di VisualTreeHelper.GetChildrenCount (obj) risolverà il problema. Tuttavia è utile essere considerati come un'opzione.
Vasil Popov,

9

Ho scoperto che la linea, VisualTreeHelper.GetChildrenCount(depObj);usata in diversi esempi sopra, non restituisce un conteggio diverso da zero per GroupBoxes, in particolare, dove GroupBoxcontiene a Gride Gridcontiene gli elementi figlio. Credo che ciò possa essere dovuto al fatto che GroupBoxnon è consentito contenere più di un figlio e questo è archiviato nella sua Contentproprietà. Non esiste un GroupBox.Childrentipo di proprietà. Sono sicuro di non averlo fatto in modo molto efficiente, ma ho modificato il primo esempio di "FindVisualChildren" in questa catena come segue:

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

Per ottenere un elenco di tutti i bambini di un tipo specifico è possibile utilizzare:

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

Piccola modifica alla ricorsione in modo da poter ad esempio trovare il controllo tab figlio di un controllo tab.

    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

Ecco un'altra versione compatta, con la sintassi generica:

    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

Ed è così che funziona verso l'alto

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


1

Volevo aggiungere un commento, ma ho meno di 50 punti, quindi posso solo "rispondere". Tenere presente che se si utilizza il metodo "VisualTreeHelper" per recuperare gli oggetti XAML "TextBlock", verranno catturati anche gli oggetti "Button" XAML. Se si reinizializza l'oggetto "TextBlock" scrivendo nel parametro Textblock.Text, non sarà più possibile modificare il testo del pulsante utilizzando il parametro Button.Content. Il pulsante mostrerà in modo permanente il testo scritto su di esso dal blocco di testo. Azione di scrittura del testo (da quando è stato recuperato -

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

Per ovviare a questo, puoi provare a utilizzare un "TextBox" XAML e aggiungere metodi (o Eventi) per simulare un pulsante XAMAL. XAML "TextBox" non viene raccolto da una ricerca di "TextBlock".


Questa è la differenza tra l'albero visivo e quello logico. L'albero visivo contiene tutti i controlli (inclusi quelli di cui è costituito un controllo, che sono definiti nel modello dei controlli) mentre l'albero logico contiene solo i controlli effettivi (senza quelli definiti nei modelli). C'è una bella visualizzazione di questo concetto qui: link
lauxjpn

1

La mia versione per 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

Per qualche motivo, nessuna delle risposte pubblicate qui mi ha aiutato a ottenere tutti i controlli di un determinato tipo contenuti in un dato controllo nella mia finestra principale. Avevo bisogno di trovare tutte le voci di menu in un menu per iterarle. Non erano tutti discendenti diretti del menu, quindi sono riuscito a collezionare solo i primi lilne usando uno dei codici sopra. Questo metodo di estensione è la mia soluzione al problema per chiunque continuerà a leggere fino in fondo qui.

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

Spero che sia d'aiuto.


1

La risposta accettata restituisce gli elementi scoperti più o meno non ordinati , seguendo il primo ramo figlio il più in profondità possibile, restituendo gli elementi rilevati lungo il percorso, prima di tornare indietro e ripetere i passaggi per i rami degli alberi non ancora analizzati.

Se hai bisogno degli elementi discendenti in ordine decrescente , in cui i figli diretti saranno ceduti per primi, quindi i loro figli e così via, funzionerà il seguente algoritmo:

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

Gli elementi risultanti verranno ordinati dal più vicino al più lontano. Ciò sarà utile, ad esempio, se stai cercando l'elemento figlio più vicino di qualche tipo e condizione:

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

1
Manca qualcosa; childnon è definito.
codebender

1

@Bryce, davvero bella risposta.

Versione 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

Utilizzo (questo disabilita tutte le TextBox in una finestra):

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

-1

L'ho trovato più facile senza Visual Tree Helpers:

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

3
Questo raggiunge solo un livello di profondità. in XAML hai controlli profondamente nidificati.
SQL Police,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.