Respostas:
Combinei o formato do modelo usado pelo algoritmo de John Myczek e Tri Q acima para criar um algoritmo findChild que pode ser usado em qualquer pai. Lembre-se de que pesquisar recursivamente uma árvore para baixo pode ser um processo demorado. Eu só verifiquei isso em um aplicativo WPF, por favor, comente sobre quaisquer erros que você possa encontrar e eu corrigirei meu código.
O WPF Snoop é uma ferramenta útil para examinar a árvore visual - eu recomendo fortemente usá-lo durante o teste ou usando esse algoritmo para verificar seu trabalho.
Há um pequeno erro no algoritmo do Tri Q. Depois que o filho for encontrado, se childrenCount for> 1 e se iterarmos novamente, podemos substituir o filho encontrado corretamente. Portanto, adicionei um if (foundChild != null) break;no meu código para lidar com essa condição.
/// <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;
}
Chame assim:
TextBox foundTextBox =
UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");
Nota Application.Current.MainWindowpode ser qualquer janela pai.
FrameworkElementcomo T, ele retornará nulo assim que o primeiro loop terminar. então você precisará fazer algumas modificações.
Você também pode encontrar um elemento pelo nome usando FrameworkElement.FindName (string) .
Dado:
<UserControl ...>
<TextBlock x:Name="myTextBlock" />
</UserControl>
No arquivo code-behind, você pode escrever:
var myTextBlock = (TextBlock)this.FindName("myTextBlock");
Obviamente, como é definido usando x: Name, você pode apenas fazer referência ao campo gerado, mas talvez queira pesquisá-lo dinamicamente e não estaticamente.
Essa abordagem também está disponível para modelos, nos quais o item nomeado aparece várias vezes (uma vez por uso do modelo).
Você pode usar o VisualTreeHelper para encontrar controles. Abaixo está um método que usa o VisualTreeHelper para localizar um controle pai de um tipo especificado. Você pode usar o VisualTreeHelper para encontrar controles de outras maneiras também.
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);
}
}
}
Chame assim:
Window owner = UIHelper.FindVisualParent<Window>(myControl);
Talvez eu esteja repetindo todos os outros, mas tenho um belo pedaço de código que estende a classe DependencyObject com um método FindChild () que fornecerá o filho por tipo e nome. Basta incluir e usar.
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;
}
}
Espero que você ache útil.
Minhas extensões para o código.
Fonte: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities
Postagem explicativa do blog: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html
Se você deseja encontrar TODOS os controles de um tipo específico, também pode estar interessado neste snippet
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;
}
}
}
childuma segunda vez? Se você é childTypedo tipo T, pode escrever dentro de if: yield return childType... não?
Isso descartará alguns elementos - você deve estendê-lo assim para oferecer suporte a uma variedade maior de controles. Para uma breve discussão, dê uma olhada aqui
/// <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 para retornar boole ter um outparâmetro que retorna o tipo em questão, como acontece com:bool IDictionary.TryGetValue(TKey key, out TValue value)
FindParent. Esse nome para mim implica que ele poderia retornar null. O Try*prefixo é usado em todo o BCL da maneira que descrevi acima. Observe também que a maioria das outras respostas aqui usa a Find*convenção de nomenclatura. É apenas um ponto menor :)
Editei o código do CrimsonX, pois não estava funcionando com os tipos 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;
}
DependencyObjectque não é FrameworkElement, poderá lançar uma exceção. Também o uso GetChildrenCountem todas as iterações do forloop parece uma má ideia.
Embora eu ame a recursão em geral, ela não é tão eficiente quanto a iteração na programação em C #, então talvez a solução a seguir seja mais clara que a sugerida por John Myczek? Isso pesquisa uma hierarquia de um determinado controle para encontrar um controle ancestral de um tipo específico.
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;
}
Chame assim para encontrar o Windowcontrole que contém ExampleTextBox:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Aqui está o meu código para encontrar controles por Tipo enquanto controlamos a profundidade da hierarquia (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 ... Eu estava tendo um problema com o código não recorrente através dos controles do usuário. Ele estava atingindo a raiz do Grid e gerando um erro. Eu acredito que isso corrige para mim:
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();
}
Eu tenho uma função de sequência como esta (que é completamente geral):
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)));
}
Obtendo filhos imediatos:
public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
{
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
.Select(i => VisualTreeHelper.GetChild(obj, i));
}
Encontrando todas as crianças na árvore hiarárquica:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
{
return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
}
Você pode chamar isso na janela para obter todos os controles.
Depois de ter a coleção, você pode usar o LINQ (por exemplo, OfType, Where).
Como a pergunta é geral o suficiente para atrair pessoas que procuram respostas para casos muito triviais: se você quer apenas um filho em vez de um descendente, pode usar o 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, é claro, o óbvio para o loop iterando sobre Children.
Essas opções já falam sobre atravessar a Árvore Visual em C #. É possível atravessar a árvore visual no xaml, também usando a extensão de marcação RelativeSource. msdn
encontre por tipo
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
Aqui está uma solução que usa um predicado flexível:
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;
}
Você pode, por exemplo, chamá-lo assim:
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 apenas corrige o erro da resposta @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;
}
Você só precisa continuar chamando o método recursivamente se os tipos forem correspondentes, mas os nomes não (isso acontece quando você passa FrameworkElementcomo T). caso contrário, ele voltará nulle isso está errado.
Para encontrar um ancestral de um determinado tipo a partir do código, você pode 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 implementação usa iteração em vez de recursão, que pode ser um pouco mais rápida.
Se você estiver usando o C # 7, isso poderá ser um pouco menor:
[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;
}
}