Respuestas:
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.MainWindow
puede ser cualquier ventana principal.
FrameworkElement
como T, volverá nulo tan pronto como termine el primer ciclo. así que necesitarás hacer algunas modificaciones.
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).
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);
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.
Mis extensiones al código.
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
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;
}
}
}
child
segunda vez? Si tiene childType
de tipo T
, puede escribir dentro de if
: yield return childType
... ¿no?
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);
}
}
Try*
método regrese bool
y tenga un out
parámetro que devuelva el tipo en cuestión, como con:bool IDictionary.TryGetValue(TKey key, out TValue value)
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 :)
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;
}
DependencyObject
que no es un FrameworkElement
puede lanzar una excepción. También usar GetChildrenCount
en cada iteración del for
bucle suena como una mala idea.
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 Window
contiene un control llamado ExampleTextBox
:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
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();
}
}
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();
}
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).
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.
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>}}}"
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;
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 FrameworkElement
como T
). de lo contrario va a volver null
y eso está mal.
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;
}
}