Risposte:
Ho combinato il formato modello utilizzato da John Myczek e l'algoritmo di Tri Q sopra per creare un algoritmo findChild che può essere utilizzato su qualsiasi genitore. Tieni presente che la ricerca ricorsiva di un albero verso il basso potrebbe essere un processo lungo. Ho controllato questo solo su un'applicazione WPF, per favore commenta tutti gli errori che potresti trovare e correggerò il mio codice.
WPF Snoop è uno strumento utile per guardare l'albero visivo: consiglio vivamente di usarlo durante i test o di utilizzare questo algoritmo per verificare il tuo lavoro.
C'è un piccolo errore nell'algoritmo di Tri Q. Dopo che il bambino è stato trovato, se childrenCount è> 1 e ripetiamo, possiamo sovrascrivere il bambino trovato correttamente. Pertanto ho aggiunto a if (foundChild != null) break;
nel mio codice per gestire questa condizione.
/// <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;
}
Chiamalo così:
TextBox foundTextBox =
UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");
Nota Application.Current.MainWindow
può essere qualsiasi finestra principale.
FrameworkElement
come T, restituirà null non appena termina il primo ciclo. quindi dovrai apportare alcune modifiche.
Puoi anche trovare un elemento per nome usando FrameworkElement.FindName (stringa) .
Dato:
<UserControl ...>
<TextBlock x:Name="myTextBlock" />
</UserControl>
Nel file code-behind, è possibile scrivere:
var myTextBlock = (TextBlock)this.FindName("myTextBlock");
Naturalmente, poiché è definito usando x: Name, potresti semplicemente fare riferimento al campo generato, ma forse vuoi cercarlo in modo dinamico piuttosto che statico.
Questo approccio è disponibile anche per i modelli, in cui l'elemento denominato viene visualizzato più volte (una volta per utilizzo del modello).
È possibile utilizzare VisualTreeHelper per trovare i controlli. Di seguito è riportato un metodo che utilizza VisualTreeHelper per trovare un controllo parent di un tipo specificato. È possibile utilizzare VisualTreeHelper per trovare i controlli anche in altri modi.
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);
}
}
}
Chiamalo così:
Window owner = UIHelper.FindVisualParent<Window>(myControl);
Potrei semplicemente ripetere tutti gli altri, ma ho un bel pezzo di codice che estende la classe DependencyObject con un metodo FindChild () che ti porterà il bambino per tipo e nome. Basta includere e utilizzare.
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;
}
}
Spero lo trovi utile.
Le mie estensioni al codice.
Fonte: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities
Post di blog esplicativo: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html
Se desideri trovare TUTTI i controlli di un tipo specifico, potresti essere interessato anche a questo frammento
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
una seconda volta? Se hai childType
del tipo T
, puoi scrivere all'interno di if
: yield return childType
... no?
Ciò eliminerà alcuni elementi: è necessario estenderlo in questo modo per supportare una gamma più ampia di controlli. Per una breve discussione, dai un'occhiata qui
/// <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*
metodo ritorni bool
e abbia un out
parametro che restituisce il tipo in questione, come nel caso di:bool IDictionary.TryGetValue(TKey key, out TValue value)
FindParent
. Questo nome per me implica che potrebbe tornare null
. Il Try*
prefisso viene utilizzato in tutto il BCL nel modo che descrivo sopra. Nota inoltre che la maggior parte delle altre risposte qui utilizza la Find*
convenzione di denominazione. È solo un piccolo aspetto però :)
Ho modificato il codice di CrimsonX perché non funzionava con i tipi di 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
che non è un FrameworkElement
, può generare un'eccezione. Anche l'utilizzo GetChildrenCount
su ogni iterazione del for
loop sembra una cattiva idea.
Sebbene io ami la ricorsione in generale, non è efficiente come l'iterazione durante la programmazione in C #, quindi forse la seguente soluzione è più chiara di quella suggerita da John Myczek? Ciò ricerca una gerarchia da un determinato controllo per trovare un controllo antenato di un determinato tipo.
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;
}
Chiamalo così per trovare il Window
controllo contenente un chiamato chiamato ExampleTextBox
:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Ecco il mio codice per trovare i controlli per Tipo controllando quanto in profondità andiamo nella gerarchia (maxDepth == 0 significa infinitamente profondo).
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 ... Ho riscontrato un problema con il codice che non ricorreva attraverso i controlli utente. Stava colpendo la radice della griglia e generando un errore. Credo che questo risolva il problema per me:
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();
}
Ho una funzione di sequenza come questa (che è completamente generale):
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)));
}
Ottenere figli immediati:
public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
{
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
.Select(i => VisualTreeHelper.GetChild(obj, i));
}
Trovare tutti i bambini lungo l'albero iararchico:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
{
return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
}
Puoi chiamarlo sulla finestra per ottenere tutti i controlli.
Dopo aver ottenuto la raccolta, è possibile utilizzare LINQ (ovvero OfType, Where).
Poiché la domanda è abbastanza generica da attirare le persone in cerca di risposte a casi molto banali: se vuoi solo un figlio anziché un discendente, puoi usare 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 ovviamente l'ovvio per il ciclo che itera su Children.
Queste opzioni parlano già di attraversare il Visual Tree in C #. È possibile attraversare l'albero visivo anche in xaml usando l'estensione di markup RelativeSource.msdn
trova per tipo
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
Ecco una soluzione che utilizza un predicato flessibile:
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;
}
Ad esempio puoi chiamarlo così:
var child = FindChild(parent, child =>
{
var textBlock = child as TextBlock;
if (textBlock != null && textBlock.Name == "MyTextBlock")
return true;
else
return false;
}) as TextBlock;
Questo codice risolve semplicemente il bug della risposta di @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;
}
Devi solo continuare a chiamare il metodo in modo ricorsivo se i tipi corrispondono, ma i nomi no (questo accade quando passi FrameworkElement
come T
). altrimenti tornerà null
e questo è sbagliato.
Per trovare un antenato di un determinato tipo dal codice, puoi usare:
[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;
}
}
Questa implementazione utilizza l'iterazione invece della ricorsione che può essere leggermente più veloce.
Se stai usando C # 7, questo può essere leggermente ridotto:
[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;
}
}