¿Cómo puedo encontrar controles WPF por nombre o tipo?


264

Necesito buscar en una jerarquía de control de WPF controles que coincidan con un nombre o tipo dado. ¿Cómo puedo hacer esto?

Respuestas:


311

Combiné el formato de plantilla utilizado por John Myczek y el algoritmo de Tri Q anterior para crear un algoritmo findChild que se puede utilizar en cualquier padre. Tenga en cuenta que la búsqueda recursiva de un árbol hacia abajo podría ser un proceso largo. Solo he verificado esto en una aplicación WPF, comente cualquier error que pueda encontrar y corregiré mi código.

WPF Snoop es una herramienta útil para mirar el árbol visual. Recomiendo encarecidamente usarlo durante las pruebas o usar este algoritmo para verificar su trabajo.

Hay un pequeño error en el algoritmo de Tri Q. Después de encontrar el elemento secundario, si childrenCount es> 1 e iteramos nuevamente, podemos sobrescribir el elemento secundario encontrado correctamente. Por lo tanto, agregué un if (foundChild != null) break;en mi código para lidiar con esta condición.

/// <summary>
/// Finds a Child of a given item in the visual tree. 
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter. 
/// If not matching item can be found, 
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName)
   where T : DependencyObject
{    
  // Confirm parent and childName are valid. 
  if (parent == null) return null;

  T foundChild = null;

  int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
  for (int i = 0; i < childrenCount; i++)
  {
    var child = VisualTreeHelper.GetChild(parent, i);
    // If the child is not of the request child type child
    T childType = child as T;
    if (childType == null)
    {
      // recursively drill down the tree
      foundChild = FindChild<T>(child, childName);

      // If the child is found, break so we do not overwrite the found child. 
      if (foundChild != null) break;
    }
    else if (!string.IsNullOrEmpty(childName))
    {
      var frameworkElement = child as FrameworkElement;
      // If the child's name is set for search
      if (frameworkElement != null && frameworkElement.Name == childName)
      {
        // if the child's name is of the request name
        foundChild = (T)child;
        break;
      }
    }
    else
    {
      // child element found.
      foundChild = (T)child;
      break;
    }
  }

  return foundChild;
}

Llámalo así:

TextBox foundTextBox = 
   UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");

La nota Application.Current.MainWindowpuede ser cualquier ventana principal.


@CrimsonX: Tal vez estoy haciendo esto mal ... Tenía una necesidad similar donde necesitaba llegar a un control (ListBox) dentro de un ContentControl (Expander). El código anterior no funcionó para mí tal como está ... Tuve que actualizar el código anterior para ver si un nodo hoja (GetChildrenCount => 0) es un ContentControl. En caso afirmativo, verifique si el contenido coincide con los criterios de nombre + tipo.
Gishu

@Gishu - Creo que debería funcionar para este propósito. ¿Puedes copiar y pegar tu código para mostrar cómo estás usando la llamada? Esperaría que fuera FindChild <ListBox> (Expander myExpanderName, "myListBoxName").
CrimsonX

3
@CrimsonX Creo que encontré otro caso de esquina. Intenté encontrar el PART_SubmenuPlaceholder en RibbonApplicationMenuItem, pero el código anterior no funcionaba. Para resolverlo, necesitaba agregar lo siguiente: if (name == ElementName) else {foundChild = FindChild (child, name) if (foundChild! = Null) break; }
kevindaub

66
Tenga cuidado, hay un error o más en la respuesta. Se detendrá tan pronto como llegue a un elemento secundario del tipo buscado. Creo que deberías considerar / priorizar otras respuestas.
Eric Ouellet

2
Este código es excelente, pero no funcionará si no está buscando un tipo específico de elemento, por ejemplo, si pasa FrameworkElementcomo T, volverá nulo tan pronto como termine el primer ciclo. así que necesitarás hacer algunas modificaciones.
Amir Oveisi

131

También puede encontrar un elemento por nombre usando FrameworkElement.FindName (string) .

Dado:

<UserControl ...>
    <TextBlock x:Name="myTextBlock" />
</UserControl>

En el archivo de código subyacente, podría escribir:

var myTextBlock = (TextBlock)this.FindName("myTextBlock");

Por supuesto, debido a que se define usando x: Name, puede hacer referencia al campo generado, pero quizás desee buscarlo dinámicamente en lugar de hacerlo estáticamente.

Este enfoque también está disponible para plantillas, en las que el elemento nombrado aparece varias veces (una vez por uso de la plantilla).


66
Para que esto funcione, no necesariamente tiene que agregar "x:" al atributo de nombre.
Brian Buck

3
Esto no parece funcionar siempre. Tengo UserControls que se combinan mediante programación en cuadrículas anidadas como el contenido de una ventana de propiedades. Sin embargo, la respuesta de CrimsonX funciona bien.
Matt

44
Esto no funcionará para elementos dentro de ItemControls, ListBoxes, etc.
Sorensen

67

Puede usar VisualTreeHelper para buscar controles. A continuación se muestra un método que utiliza VisualTreeHelper para encontrar un control primario de un tipo específico. También puede usar VisualTreeHelper para buscar controles de otras maneras.

public static class UIHelper
{
   /// <summary>
   /// Finds a parent of a given item on the visual tree.
   /// </summary>
   /// <typeparam name="T">The type of the queried item.</typeparam>
   /// <param name="child">A direct or indirect child of the queried item.</param>
   /// <returns>The first parent item that matches the submitted type parameter. 
   /// If not matching item can be found, a null reference is being returned.</returns>
   public static T FindVisualParent<T>(DependencyObject child)
     where T : DependencyObject
   {
      // get parent item
      DependencyObject parentObject = VisualTreeHelper.GetParent(child);

      // we’ve reached the end of the tree
      if (parentObject == null) return null;

      // check if the parent matches the type we’re looking for
      T parent = parentObject as T;
      if (parent != null)
      {
         return parent;
      }
      else
      {
         // use recursion to proceed with next level
         return FindVisualParent<T>(parentObject);
      }
   }
}

Llámalo así:

Window owner = UIHelper.FindVisualParent<Window>(myControl);

¿Cómo se obtiene o qué es myControl?
Demodave el

21

Puede que solo esté repitiendo a todos los demás, pero tengo un bonito código que extiende la clase DependencyObject con un método FindChild () que le dará al niño por tipo y nombre. Solo incluye y usa.

public static class UIChildFinder
{
    public static DependencyObject FindChild(this DependencyObject reference, string childName, Type childType)
    {
        DependencyObject foundChild = null;
        if (reference != null)
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(reference);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(reference, i);
                // If the child is not of the request child type child
                if (child.GetType() != childType)
                {
                    // recursively drill down the tree
                    foundChild = FindChild(child, childName, childType);
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name
                        foundChild = child;
                        break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = child;
                    break;
                }
            }
        }
        return foundChild;
    }
}

Esperamos que te sea útil.


2
Según mi publicación anterior, hay un pequeño error de implementación en su código: stackoverflow.com/questions/636383/wpf-ways-to-find-controls/…
CrimsonX el

18

Mis extensiones al código.

  • Se agregaron sobrecargas para buscar un hijo por tipo, por tipo y criterio (predicado), encontrar todos los hijos de tipo que cumplan con los criterios
  • el método FindChildren es un iterador además de ser un método de extensión para DependencyObject
  • FindChildren recorre subárboles lógicos también. Vea la publicación de Josh Smith vinculada en la publicación del blog.

Fuente: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities

Publicación explicativa del blog: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html


-1 Exactamente lo que estaba a punto de implementar (predicado, iterador y método de extensión), pero hay un 404 en el enlace de origen. ¡Cambiará a +1 si el código está incluido aquí, o el enlace fuente está arreglado!
cod3monk3y

@ cod3monk3y - La migración de Git eliminó el enlace que parece :) Aquí tienes ... code.google.com/p/gishu-util/source/browse/…
Gishu

18

Si desea encontrar TODOS los controles de un tipo específico, también podría estar interesado en este fragmento

    public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) 
        where T : DependencyObject
    {
        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);

            var childType = child as T;
            if (childType != null)
            {
                yield return (T)child;
            }

            foreach (var other in FindVisualChildren<T>(child))
            {
                yield return other;
            }
        }
    }

3
Buena, pero asegúrese de que el control esté cargado; de lo contrario, GetChildrenCount devolverá 0.
Klaus Nji

@UrbanEsc, ¿por qué lanzas por childsegunda vez? Si tiene childTypede tipo T, puede escribir dentro de if: yield return childType... ¿no?
Massimiliano Kraus

@MassimilianoKraus Hola, perdón por la respuesta tardía, pero tienes razón. Me lo atribuyo reescribir este fragmento varias veces, y por lo tanto, esto podría ser un fragmento de un cheque diferente
UrbanEsc

16

Esto descartará algunos elementos: debe extenderlo de esta manera para admitir una gama más amplia de controles. Para una breve discusión, eche un vistazo aquí

 /// <summary>
 /// Helper methods for UI-related tasks.
 /// </summary>
 public static class UIHelper
 {
   /// <summary>
   /// Finds a parent of a given item on the visual tree.
   /// </summary>
   /// <typeparam name="T">The type of the queried item.</typeparam>
   /// <param name="child">A direct or indirect child of the
   /// queried item.</param>
   /// <returns>The first parent item that matches the submitted
   /// type parameter. If not matching item can be found, a null
   /// reference is being returned.</returns>
   public static T TryFindParent<T>(DependencyObject child)
     where T : DependencyObject
   {
     //get parent item
     DependencyObject parentObject = GetParentObject(child);

     //we've reached the end of the tree
     if (parentObject == null) return null;

     //check if the parent matches the type we're looking for
     T parent = parentObject as T;
     if (parent != null)
     {
       return parent;
     }
     else
     {
       //use recursion to proceed with next level
       return TryFindParent<T>(parentObject);
     }
   }

   /// <summary>
   /// This method is an alternative to WPF's
   /// <see cref="VisualTreeHelper.GetParent"/> method, which also
   /// supports content elements. Do note, that for content element,
   /// this method falls back to the logical tree of the element!
   /// </summary>
   /// <param name="child">The item to be processed.</param>
   /// <returns>The submitted item's parent, if available. Otherwise
   /// null.</returns>
   public static DependencyObject GetParentObject(DependencyObject child)
   {
     if (child == null) return null;
     ContentElement contentElement = child as ContentElement;

     if (contentElement != null)
     {
       DependencyObject parent = ContentOperations.GetParent(contentElement);
       if (parent != null) return parent;

       FrameworkContentElement fce = contentElement as FrameworkContentElement;
       return fce != null ? fce.Parent : null;
     }

     //if it's not a ContentElement, rely on VisualTreeHelper
     return VisualTreeHelper.GetParent(child);
   }
}

55
Por convención, esperaría que cualquier Try*método regrese booly tenga un outparámetro que devuelva el tipo en cuestión, como con:bool IDictionary.TryGetValue(TKey key, out TValue value)
Drew Noakes

@DrewNoakes, ¿cómo sugieres que se llame Philipp? Además, incluso con tal expectativa, encuentro que su código es claro y claro para usar.
ANeves

1
@ANeves, en este caso simplemente lo llamaría FindParent. Este nombre para mí implica que podría volver null. El Try*prefijo se usa en todo el BCL de la manera que describí anteriormente. También tenga en cuenta que la mayoría de las otras respuestas aquí usan la Find*convención de nomenclatura. Sin embargo, es solo un punto menor :)
Drew Noakes

16

Edité el código de CrimsonX ya que no funcionaba con los tipos de superclase:

public static T FindChild<T>(DependencyObject depObj, string childName)
   where T : DependencyObject
{
    // Confirm obj is valid. 
    if (depObj == null) return null;

    // success case
    if (depObj is T && ((FrameworkElement)depObj).Name == childName)
        return depObj as T;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(depObj, i);

        //DFS
        T obj = FindChild<T>(child, childName);

        if (obj != null)
            return obj;
    }

    return null;
}

1
Si pasa este método un DependencyObjectque no es un FrameworkElementpuede lanzar una excepción. También usar GetChildrenCounten cada iteración del forbucle suena como una mala idea.
Tim Pohlmann

1
bueno, esto es de hace 5 años, así que ni siquiera sé si funciona más :)
andresp

Lo acabo de mencionar, porque me topé con él y otros también pudieron;)
Tim Pohlmann

13

Si bien me encanta la recursividad en general, no es tan eficiente como la iteración cuando se programa en C #, entonces ¿tal vez la siguiente solución es mejor que la sugerida por John Myczek? Esto busca una jerarquía desde un control dado para encontrar un control ancestro de un tipo particular.

public static T FindVisualAncestorOfType<T>(this DependencyObject Elt)
    where T : DependencyObject
{
    for (DependencyObject parent = VisualTreeHelper.GetParent(Elt);
        parent != null; parent = VisualTreeHelper.GetParent(parent))
    {
        T result = parent as T;
        if (result != null)
            return result;
    }
    return null;
}

Llámalo así para encontrar el que Windowcontiene un control llamado ExampleTextBox:

Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();

9

Aquí está mi código para encontrar controles por Tipo mientras controlamos qué tan profundo llegamos a la jerarquía (maxDepth == 0 significa infinitamente profundo).

public static class FrameworkElementExtension
{
    public static object[] FindControls(
        this FrameworkElement f, Type childType, int maxDepth)
    {
        return RecursiveFindControls(f, childType, 1, maxDepth);
    }

    private static object[] RecursiveFindControls(
        object o, Type childType, int depth, int maxDepth = 0)
    {
        List<object> list = new List<object>();
        var attrs = o.GetType()
            .GetCustomAttributes(typeof(ContentPropertyAttribute), true);
        if (attrs != null && attrs.Length > 0)
        {
            string childrenProperty = (attrs[0] as ContentPropertyAttribute).Name;
            foreach (var c in (IEnumerable)o.GetType()
                .GetProperty(childrenProperty).GetValue(o, null))
            {
                if (c.GetType().FullName == childType.FullName)
                    list.Add(c);
                if (maxDepth == 0 || depth < maxDepth)
                    list.AddRange(RecursiveFindControls(
                        c, childType, depth + 1, maxDepth));
            }
        }
        return list.ToArray();
    }
}

9

exciton80 ... Estaba teniendo un problema con su código no recurrente a través de los controles de usuario. Estaba golpeando la raíz de Grid y arrojando un error. Creo que esto me lo soluciona:

public static object[] FindControls(this FrameworkElement f, Type childType, int maxDepth)
{
    return RecursiveFindControls(f, childType, 1, maxDepth);
}

private static object[] RecursiveFindControls(object o, Type childType, int depth, int maxDepth = 0)
{
    List<object> list = new List<object>();
    var attrs = o.GetType().GetCustomAttributes(typeof(ContentPropertyAttribute), true);
    if (attrs != null && attrs.Length > 0)
    {
        string childrenProperty = (attrs[0] as ContentPropertyAttribute).Name;
        if (String.Equals(childrenProperty, "Content") || String.Equals(childrenProperty, "Children"))
        {
            var collection = o.GetType().GetProperty(childrenProperty).GetValue(o, null);
            if (collection is System.Windows.Controls.UIElementCollection) // snelson 6/6/11
            {
                foreach (var c in (IEnumerable)collection)
                {
                    if (c.GetType().FullName == childType.FullName)
                        list.Add(c);
                    if (maxDepth == 0 || depth < maxDepth)
                        list.AddRange(RecursiveFindControls(
                            c, childType, depth + 1, maxDepth));
                }
            }
            else if (collection != null && collection.GetType().BaseType.Name == "Panel") // snelson 6/6/11; added because was skipping control (e.g., System.Windows.Controls.Grid)
            {
                if (maxDepth == 0 || depth < maxDepth)
                    list.AddRange(RecursiveFindControls(
                        collection, childType, depth + 1, maxDepth));
            }
        }
    }
    return list.ToArray();
}

8

Tengo una función de secuencia como esta (que es completamente general):

    public static IEnumerable<T> SelectAllRecursively<T>(this IEnumerable<T> items, Func<T, IEnumerable<T>> func)
    {
        return (items ?? Enumerable.Empty<T>()).SelectMany(o => new[] { o }.Concat(SelectAllRecursively(func(o), func)));
    }

Conseguir hijos inmediatos:

    public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
    {
        return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
            .Select(i => VisualTreeHelper.GetChild(obj, i));
    }

Encontrar a todos los niños en el árbol jerárquico:

    public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
    {
        return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
    }

Puede llamar a esto en la ventana para obtener todos los controles.

Después de tener la colección, puede usar LINQ (es decir, OfType, Where).


6

Dado que la pregunta es lo suficientemente general como para atraer a las personas que buscan respuestas a casos muy triviales: si solo quieres un hijo en lugar de un descendiente, puedes usar Linq:

private void ItemsControlItem_Loaded(object sender, RoutedEventArgs e)
{
    if (SomeCondition())
    {
        var children = (sender as Panel).Children;
        var child = (from Control child in children
                 where child.Name == "NameTextBox"
                 select child).First();
        child.Focus();
    }
}

o, por supuesto, lo obvio para iteración de bucle sobre niños.


3

Estas opciones ya hablan de atravesar el árbol visual en C #. Es posible atravesar el árbol visual en xaml también usando la extensión de marcado RelativeSource.msdn

buscar por tipo

Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}" 

2

Aquí hay una solución que usa un predicado flexible:

public static DependencyObject FindChild(DependencyObject parent, Func<DependencyObject, bool> predicate)
{
    if (parent == null) return null;

    int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < childrenCount; i++)
    {
        var child = VisualTreeHelper.GetChild(parent, i);

        if (predicate(child))
        {
            return child;
        }
        else
        {
            var foundChild = FindChild(child, predicate);
            if (foundChild != null)
                return foundChild;
        }
    }

    return null;
}

Por ejemplo, puede llamarlo así:

var child = FindChild(parent, child =>
{
    var textBlock = child as TextBlock;
    if (textBlock != null && textBlock.Name == "MyTextBlock")
        return true;
    else
        return false;
}) as TextBlock;

1

Este código solo corrige el error de respuesta de @CrimsonX:

 public static T FindChild<T>(DependencyObject parent, string childName)
       where T : DependencyObject
    {    
      // Confirm parent and childName are valid. 
      if (parent == null) return null;

      T foundChild = null;

      int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
      for (int i = 0; i < childrenCount; i++)
      {
        var child = VisualTreeHelper.GetChild(parent, i);
        // If the child is not of the request child type child
        T childType = child as T;
        if (childType == null)
        {
          // recursively drill down the tree
          foundChild = FindChild<T>(child, childName);

          // If the child is found, break so we do not overwrite the found child. 
          if (foundChild != null) break;
        }
        else if (!string.IsNullOrEmpty(childName))
        {
          var frameworkElement = child as FrameworkElement;
          // If the child's name is set for search
          if (frameworkElement != null && frameworkElement.Name == childName)
          {
            // if the child's name is of the request name
            foundChild = (T)child;
            break;
          }

 // recursively drill down the tree
          foundChild = FindChild<T>(child, childName);

          // If the child is found, break so we do not overwrite the found child. 
          if (foundChild != null) break;


        else
        {
          // child element found.
          foundChild = (T)child;
          break;
        }
      }

      return foundChild;
    }  

Solo necesita continuar llamando al método de forma recursiva si los tipos coinciden pero los nombres no (esto sucede cuando se pasa FrameworkElementcomo T). de lo contrario va a volver nully eso está mal.


0

Para encontrar un antepasado de un tipo dado a partir del código, puede usar:

[CanBeNull]
public static T FindAncestor<T>(DependencyObject d) where T : DependencyObject
{
    while (true)
    {
        d = VisualTreeHelper.GetParent(d);

        if (d == null)
            return null;

        var t = d as T;

        if (t != null)
            return t;
    }
}

Esta implementación utiliza la iteración en lugar de la recursividad, que puede ser un poco más rápida.

Si está utilizando C # 7, esto puede hacerse un poco más corto:

[CanBeNull]
public static T FindAncestor<T>(DependencyObject d) where T : DependencyObject
{
    while (true)
    {
        d = VisualTreeHelper.GetParent(d);

        if (d == null)
            return null;

        if (d is T t)
            return t;
    }
}

-5

Prueba esto

<TextBlock x:Name="txtblock" FontSize="24" >Hai Welcom to this page
</TextBlock>

Código detrás

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
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.