Comment puis-je trouver les contrôles WPF par nom ou par type?

j'ai besoin de chercher dans une hiérarchie de contrôle WPF les contrôles qui correspondent à un nom donné ou à un type donné. Comment puis-je faire cela?

234
demandé sur Shog9 2009-03-12 00:08:25

18 réponses

j'ai combiné le format de modèle utilisé par John Myczek et l'algorithme de Tri Q ci-dessus pour créer un algorithme findChild qui peut être utilisé sur n'importe quel parent. Gardez à l'esprit que la recherche récursive d'un arbre vers le bas pourrait être un long processus. Je n'ai vérifié cela que par hasard sur une application WPF, s'il vous plaît commenter sur les erreurs que vous pourriez trouver et je vais corriger mon code.

WPF Snoop est un outil utile dans la recherche à l'arborescence visuelle -, je recommanderais fortement en l'utilisant pendant les tests ou en utilisant cet algorithme pour vérifier votre travail.

il y a une petite erreur dans L'algorithme de tri Q. après que l'enfant est trouvé, si le nombre d'enfants est > 1 et que nous itérons à nouveau nous pouvons écraser l'enfant correctement trouvé. Par conséquent, j'ai ajouté un if (foundChild != null) break; dans mon code pour traiter cette condition.

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

appelez ça comme ça:

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

Note Application.Current.MainWindow peut être de la fenêtre parent.

279
répondu CrimsonX 2016-01-28 13:31:05

vous pouvez également trouver un élément par son nom en utilisant Frameworkkelement.FindName (string) .

:

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

dans le fichier code-behind, vous pouvez écrire:

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

bien sûr, parce qu'il est défini en utilisant x:Name, vous pouvez juste faire référence au champ généré, mais peut-être que vous voulez le chercher dynamiquement plutôt que statiquement.

Cette approche est également disponible pour les modèles, dans lequel l'élément nommé apparaît plusieurs fois (une fois par l'utilisation du modèle).

112
répondu Drew Noakes 2009-09-25 12:48:51

vous pouvez utiliser le VisualTreeHelper pour trouver des commandes. Ci-dessous est une méthode qui utilise le VisualTreeHelper pour trouver un contrôle parent d'un type spécifié. Vous pouvez utiliser le VisualTreeHelper pour trouver des commandes d'autres manières.

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

appelez ça comme ça:

Window owner = UIHelper.FindVisualParent<Window>(myControl);
60
répondu John Myczek 2011-01-31 19:52:44

Je ne fais peut-être que répéter tout le monde, mais j'ai un joli morceau de code qui étend la classe DependencyObject avec une méthode FindChild() qui vous donnera l'enfant par type et nom. Il suffit d'inclure et d'utilisation.

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

J'espère que vous le trouverez utile.

19
répondu Tri Q Tran 2009-10-01 01:47:43

mes extensions au code.

  • ajouté des surcharges pour trouver un enfant par type, par type et critères (prédicat), trouver tous les enfants de type qui répondent aux critères
  • la méthode FindChildren est un itérateur en plus d'être une méthode d'extension pour DependencyObject
  • les enfants trouvent aussi des sous-arbres logiques. Voir le billet de Josh Smith sur le blog.

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

article de blog explicatif : http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html

17
répondu Gishu 2015-09-10 09:12:01

Si vous voulez trouver TOUS les contrôles d'un type spécifique, vous pourriez être intéressé par cet extrait trop

    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;
            }
        }
    }
16
répondu UrbanEsc 2015-09-28 13:23:18

j'ai édité le code de CrimsonX car il ne fonctionnait pas avec les types de superclasse:

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;
}
15
répondu andresp 2010-06-10 13:57:32

ceci rejettera certains éléments - vous devriez l'étendre comme ceci afin de supporter un plus large éventail de contrôles. Pour une brève discussion, avoir un oeil ici

 /// <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);
   }
}
14
répondu Philipp 2009-06-25 09:40:10

bien que j'aime la récursion en général, ce n'est pas aussi efficace que l'itération lors de la programmation en C#, donc peut-être la solution suivante est plus claire que celle suggérée par John Myczek ci-dessus?

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;
}
11
répondu Nathan Phillips 2011-09-06 12:31:17

Voici mon code pour trouver les contrôles par Type tout en contrôlant jusqu'où nous allons dans la hiérarchie (maxDepth == 0 signifie infiniment profond).

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();
    }
}
8
répondu exciton80 2010-10-20 10:17:24

exciton80... J'avais un problème avec le fait que votre code ne se reproduise pas à travers usercontrols. Il frappait la racine de la grille et jetait une erreur. Je crois que cela me corrige:

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
répondu Shawn Nelson 2011-06-07 05:17:28

j'ai une fonction de séquence comme celle-ci (qui est complètement générale):

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

Obtention immédiate des enfants:

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

trouver tous les enfants en bas de l'arbre hiararchique:

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

vous pouvez appeler cela sur la fenêtre pour obtenir tous les contrôles.

après avoir la collection, vous pouvez utiliser LINQ (i.e. OfType, Where).

7
répondu VB Guy 2014-08-20 15:45:01

puisque la question est assez générale qu'elle pourrait attirer les gens à la recherche de réponses à des cas très triviaux: si vous voulez juste un enfant plutôt qu'un descendant, vous pouvez utiliser 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();
    }
}

ou bien sûr l'évident pour boucle itérative sur les enfants.

5
répondu El Zorko 2011-03-06 13:03:18

Voici une solution qui utilise un prédicat 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;
}

vous pouvez par exemple l'appeler comme ceci:

var child = FindChild(parent, child =>
{
    var textBlock = child as TextBlock;
    if (textBlock != null && textBlock.Name == "MyTextBlock")
        return true;
    else
        return false;
}) as TextBlock;
2
répondu Tim Pohlmann 2015-09-09 15:54:59

ces options parlent déjà de traverser l'arbre visuel dans C#. Il est également possible de traverser l'arbre visuel de xaml en utilisant L'extension de markup RelativeSource. msdn

trouver par type

Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}" 
2
répondu Neeraj 2016-01-28 13:32:48

ce code ne fait que corriger le bug de la réponse 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;
    }  

vous avez juste besoin de continuer à appeler la méthode de façon récursive si les types correspondent mais les noms ne correspondent pas (cela se produit lorsque vous passez FrameworkElement comme T ). sinon ça va retourner null et c'est mal.

1
répondu Amir Oveisi 2017-11-21 21:17:55

pour trouver un ancêtre d'un type donné à partir du code, vous pouvez utiliser:

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

cette implémentation utilise l'itération au lieu de la récursion qui peut être légèrement plus rapide.

si vous utilisez C# 7, cela peut être légèrement raccourci:

[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;
    }
}
0
répondu Drew Noakes 2017-03-07 12:54:57

Essayez cette

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

Code Derrière

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
-4
répondu Jayasri 2015-09-09 15:59:48