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 peut être un processus long. J'ai seulement vérifié cela sur une application WPF, veuillez commenter les erreurs que vous pourriez trouver et je corrigerai mon code.
WPF Snoop est un outil utile pour regarder l'arborescence visuelle - je vous recommande fortement de l'utiliser pendant les tests ou d'utiliser 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 le nombre d'enfants est> 1 et que nous réitérons, nous pouvons remplacer l'enfant trouvé correctement. J'ai donc ajouté un if (foundChild != null) break;
dans mon code pour faire face à 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 ceci:
TextBox foundTextBox =
UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");
La note Application.Current.MainWindow
peut être n'importe quelle fenêtre parente.
FrameworkElement
en T, il retournera null dès que la première boucle se terminera. donc tu vas devoir faire quelques modifications.
Vous pouvez également rechercher un élément par son nom à l'aide de FrameworkElement.FindName (chaîne) .
Donné:
<UserControl ...>
<TextBlock x:Name="myTextBlock" />
</UserControl>
Dans le fichier code-behind, vous pouvez écrire:
var myTextBlock = (TextBlock)this.FindName("myTextBlock");
Bien sûr, car 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 de manière dynamique plutôt que statique.
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).
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 ceci:
Window owner = UIHelper.FindVisualParent<Window>(myControl);
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 nom. Il suffit d'inclure et d'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.
Mes extensions au code.
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
Si vous souhaitez trouver TOUS les contrôles d'un type spécifique, cet extrait 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;
}
}
}
child
une deuxième fois? Si vous avez childType
du type T
, vous pouvez écrire à l'intérieur du if
: yield return childType
... non?
Cela supprimera certains éléments - vous devez l'étendre comme ceci afin de prendre en charge un plus large éventail de contrôles. Pour une brève discussion, jetez un œil 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);
}
}
Try*
méthode retourne bool
et ait un out
paramètre qui retourne le type en question, comme avec:bool IDictionary.TryGetValue(TKey key, out TValue value)
FindParent
. Ce nom pour moi implique qu'il pourrait revenir null
. Le Try*
préfixe est utilisé dans toute la BCL de la manière que je décris ci-dessus. Notez également que la plupart des autres réponses ici utilisent la Find*
convention de dénomination. Ce n'est qu'un point mineur cependant :)
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;
}
DependencyObject
qui n'est pas a, FrameworkElement
elle peut lever une exception. Utiliser également à GetChildrenCount
chaque itération de la for
boucle sonne comme une mauvaise idée.
Bien que j'aime la récursivité 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 nette que celle suggérée par John Myczek? Cela 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 le Window
contenant contenant un contrôle appelé ExampleTextBox
:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Voici mon code pour trouver des contrôles par type tout en contrôlant la profondeur de 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();
}
}
exciton80 ... J'avais un problème avec votre code qui ne se reproduisait pas via les contrôles utilisateur. Il atteignait la racine de la grille et lançait 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();
}
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 hiararchique:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
{
return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
}
Vous pouvez appeler cela dans la fenêtre pour obtenir tous les contrôles.
Après avoir la collection, vous pouvez utiliser LINQ (c'est-à-dire OfType, Where).
Puisque la question est suffisamment générale pour attirer des personnes à 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'évidence pour l'itération de boucle sur les enfants.
Ces options parlent déjà de la traversée de l'arborescence visuelle en C #. Il est également possible de parcourir l'arborescence visuelle dans xaml en utilisant l'extension de balisage RelativeSource. msdn
trouver par type
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
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;
Ce code corrige simplement le bug de la réponse @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 manière récursive si les types correspondent, mais pas les noms (cela se produit lorsque vous passez en FrameworkElement
tant que T
). sinon ça va revenir null
et c'est faux.
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écursivité 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;
}
}