Encuentre todos los controles en la ventana de WPF por tipo


218

Estoy buscando una manera de encontrar todos los controles en Windows por su tipo,

por ejemplo: buscar todos TextBoxes, encontrar todos los controles que implementan una interfaz específica, etc.


mientras estamos en el tema, esto también es relevante goo.gl/i9RVx
Andrija

También escribí una publicación de blog sobre el tema: Modificación de una plantilla de control en tiempo de ejecución
Adolfo Perez

Respuestas:


430

Esto debería funcionar

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

luego enumeras los controles así

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

68
Nota: Si está tratando de hacer que esto funcione y descubra que su ventana (por ejemplo) tiene 0 elementos secundarios visuales, intente ejecutar este método en el controlador de eventos Cargado. Si lo ejecuta en el constructor (incluso después de InitializeComponent ()), los elementos visuales aún no se cargan y no funcionará.
Ryan Lundy

24
Cambiar de VisualTreeHelper a LogicalTreeHelpers también hará que se incluyan elementos invisibles.
Mathias Lykkegaard Lorenzen

11
¿No es redundante la línea "child! = Null && child is T"? En caso de que no se acaba de leer "niño es T"
noonand

1
Lo convertiría en un método de extensión con solo inscribir un thisantes DependencyObject=>this DependencyObject depObj
Johannes Wanzek

1
@JohannesWanzek No olvides que también necesitarías cambiar el bit donde lo llamas en el niño: foreach (ChildofChild.FindVisualChildren <T> ()) {bla bla bla}
Será el

66

Esta es la manera más fácil:

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

donde control es el elemento raíz de la ventana.


1
¿Qué quieres decir con "elemento raíz"? ¿Qué debo escribir para conectarme con mi formulario de ventana principal?
deadfish

Lo entiendo, en XAML ver que tenía que nombre del conjunto de rejilla <Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>y luego podría utilizarAnata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
Deadfish

68
Esto no responde la pregunta que se hizo. Solo devuelve controles secundarios de un nivel de profundidad.
Jim

21

Adapte la respuesta de @Bryce Kahle para seguir la sugerencia y el uso de @Mathias Lykkegaard Lorenzen LogicalTreeHelper.

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

(Todavía no verificará los controles de pestañas o las Cuadrículas dentro de GroupBoxes como lo mencionan @Benjamin Berry y @David R respectivamente.) (¡También siguió la sugerencia de @noonand y eliminó el hijo redundante! = Nulo)


He estado buscando durante un tiempo cómo borrar todos mis cuadros de texto, tengo varias pestañas y este es el único código que funcionó :) gracias
JohnChris

13

Use las clases auxiliares VisualTreeHelpero LogicalTreeHelpersegún el árbol que le interese. Ambas proporcionan métodos para obtener los elementos secundarios de un elemento (aunque la sintaxis difiere un poco). A menudo uso estas clases para encontrar la primera aparición de un tipo específico, pero podría modificarlo fácilmente para encontrar todos los objetos de ese 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 para explicación y publicación, pero Bryce Kahle publicó una función que funciona completamente Gracias
Andrija

Esto no soluciona el problema de la pregunta, y también la respuesta con el tipo genérico es mucho más clara. Combinándolo con el uso de VisualTreeHelper.GetChildrenCount (obj) solucionará el problema. Sin embargo, es útil ser considerado como una opción.
Vasil Popov

9

Descubrí que la línea, VisualTreeHelper.GetChildrenCount(depObj);utilizada en varios ejemplos anteriores no devuelve un recuento distinto de cero para GroupBoxes, en particular, donde GroupBoxcontiene Gridlos Gridelementos a y los elementos secundarios. Creo que esto puede deberse a que GroupBoxno se permite que contenga más de un hijo, y esto se almacena en su Contentpropiedad. No hay GroupBox.Childrentipo de propiedad. Estoy seguro de que no hice esto de manera muy eficiente, pero modifiqué el primer ejemplo "FindVisualChildren" en esta cadena de la siguiente manera:

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

Para obtener una lista de todos los hijos de un tipo específico, puede usar:

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

Pequeño cambio en la recursividad para que, por ejemplo, pueda encontrar el control de pestaña secundaria de un control de pestaña.

    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

Aquí hay otra versión compacta, con la sintaxis genérica:

    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

Y así es como funciona hacia arriba

    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

Quería agregar un comentario pero tengo menos de 50 puntos, así que solo puedo "responder". Tenga en cuenta que si utiliza el método "VisualTreeHelper" para recuperar objetos XAML "TextBlock", también capturará objetos XAML "Button". Si reinicializa el objeto "TextBlock" escribiendo en el parámetro Textblock.Text, ya no podrá cambiar el texto del botón con el parámetro Button.Content. El botón mostrará permanentemente el texto escrito desde Textblock. Acción de escritura de texto (desde cuando se recuperó -

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

Para evitar esto, puede intentar usar un "cuadro de texto" XAML y agregar métodos (o eventos) para imitar un botón XAMAL. XAML "TextBox" no se recopila mediante una búsqueda de "TextBlock".


Esa es la diferencia entre el árbol visual y el lógico. El árbol visual contiene todos los controles (incluidos aquellos de los que está hecho un control, que se definen en la plantilla de controles), mientras que el árbol lógico solo contiene los controles reales (sin los definidos en las plantillas). Hay una buena visualización de este concepto aquí: enlace
lauxjpn

1

Mi versión para 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

Por alguna razón, ninguna de las respuestas publicadas aquí me ayudó a obtener todos los controles de un tipo dado contenido en un control dado en mi MainWindow. Necesitaba encontrar todos los elementos del menú en un menú para iterarlos. No todos eran descendientes directos del menú, por lo que logré recopilar solo el primer lilne de ellos usando cualquiera de los códigos anteriores. Este método de extensión es mi solución para el problema de cualquiera que continúe leyendo hasta aquí.

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

Espero eso ayude.


1

La respuesta aceptada devuelve los elementos descubiertos más o menos desordenados , siguiendo la primera rama secundaria lo más profundo posible, al tiempo que proporciona los elementos descubiertos en el camino, antes de retroceder y repetir los pasos para las ramas de los árboles aún no analizadas.

Si necesita los elementos descendientes en orden descendente , donde los hijos directos se producirán primero, luego sus hijos, etc., funcionará el siguiente 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;
        }
    }
}

Los elementos resultantes se ordenarán del más cercano al más alejado. Esto será útil, por ejemplo, si está buscando el elemento hijo más cercano de algún tipo y condición:

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

1
¿Falta algo? childes indefinido.
codebender

1

@Bryce, muy buena respuesta.

Versión 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

Uso (esto deshabilita todos los cuadros de texto en una ventana):

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

-1

Lo encontré más fácil sin Visual Tree Helpers:

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

3
Esto va solo un nivel de profundidad. en XAML tienes controles profundamente anidados.
SQL Police
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.