web-dev-qa-db-fra.com

Comment trouver les contrôles WPF par nom ou par type?

Je dois rechercher dans une hiérarchie de contrôles WPF des contrôles correspondant à un nom ou à un type. Comment puis-je faire ceci?

255
alex2k8

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 pouvant être utilisé sur n'importe quel parent. N'oubliez pas que la recherche récursive d'un arbre vers le bas peut être un processus long. Je n’ai fait que des vérifications ponctuelles sur une application WPF. Veuillez commenter les erreurs que vous pourriez trouver et je corrigerai mon code.

WPF Snoop est un outil utile pour examiner l’arborescence visuelle. Je vous recommande fortement de l’utiliser lors des tests ou de l’utilisation de cet algorithme pour vérifier votre travail.

Il y a une petite erreur dans l'algorithme de Tri Q. Une fois l'enfant trouvé, si childrenCount 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");

Remarque Application.Current.MainWindow peut être n'importe quelle fenêtre parente.

299
CrimsonX

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

Donné:

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

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

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

Bien sûr, comme il est défini à l'aide de x: Name, vous pouvez simplement référencer le champ généré, mais vous souhaitez peut-être le rechercher dynamiquement plutôt que statiquement.

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

125
Drew Noakes

Vous pouvez utiliser VisualTreeHelper pour rechercher des contrôles. Vous trouverez ci-dessous une méthode qui utilise VisualTreeHelper pour rechercher un contrôle parent d'un type spécifié. Vous pouvez également utiliser VisualTreeHelper pour rechercher d'autres contrôles.

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);
66
John Myczek

Je répète peut-être 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 par nom. Juste inclure et utiliser.

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.

20
Tri Q Tran

Mes extensions au code.

  • Ajout de surcharges pour rechercher un enfant par type, par type et par critère (prédicat), rechercher 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
  • FindChildren parcourt également des sous-arbres logiques. Voir le message de Josh Smith dans 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

18
Gishu

Si vous voulez trouver TOUS les contrôles d'un type spécifique, cet extrait de code pourrait également vous intéresser

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

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

Certains éléments seront ignorés - vous devriez l’étendre de la sorte afin de prendre en charge un plus large éventail de contrôles. Pour une brève discussion, jetez 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);
   }
}
15
Philipp

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 #, alors peut-être que la solution suivante est plus claire que celle suggérée par John Myczek? Ceci recherche une hiérarchie à partir d'un contrôle donné pour trouver un contrôle ancêtre d'un type particulier.

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

Appelez-le comme ceci pour trouver la Window contenant un contrôle appelé ExampleTextBox:

Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
13
Nathan Phillips

Voici mon code pour rechercher des contrôles par Type tout en contrôlant la profondeur à laquelle 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();
    }
}
9
exciton80

exciton80 ... J'avais un problème avec votre code qui ne récurait pas à travers usercontrols. Il frappait la racine de la grille et jetait une erreur. Je crois que cela corrige pour moi:

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();
}
9
Shawn Nelson

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

Obtenir des enfants immédiats:

    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 dans l'arbre hiérarchique:

    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.

Une fois que vous avez la collection, vous pouvez utiliser LINQ (c'est-à-dire OfType, Where).

8
VB Guy

Comme la question est suffisamment générale pour attirer des gens qui cherchent des 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 la boucle itérant sur les enfants.

6
El Zorko

Ces options parlent déjà de parcourir l’arbre visuel en C #. Il est également possible de parcourir l'arborescence visuelle dans xaml à l'aide de l'extension de balisage RelativeSource. msdn

trouver par type

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

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
Tim Pohlmann

Ce code corrige simplement le bogue de @CrimsonX answer:

 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 devez simplement continuer à appeler la méthode de manière récursive si les types correspondent, mais les noms ne le font pas (ceci se produit lorsque vous transmettez FrameworkElement en tant que T). sinon, ça va retourner null et c'est faux.

1
Amir Oveisi

Pour trouver un ancêtre d'un type donné à partir de 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écursivité, qui peut être légèrement plus rapide.

Si vous utilisez C # 7, cela peut être légèrement plus court:

[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
Drew Noakes