Como posso encontrar controles WPF por nome ou tipo?

264

Preciso pesquisar na hierarquia de controle do WPF controles que correspondam a um determinado nome ou tipo. Como posso fazer isso?

alex2k8
fonte

Respostas:

311

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.

CrimsonX
fonte
@CrimsonX: Talvez eu esteja fazendo isso errado ... Eu tinha uma necessidade semelhante em que precisava obter um controle (ListBox) dentro de um ContentControl (Expander). O código acima não funcionou para mim como está .. Eu tive que atualizar o código acima para ver se um nó folha (GetChildrenCount => 0) é um ContentControl. Se sim, verifique se o conteúdo corresponde ao nome + aos critérios de tipo.
Gishu 24/03/10
@ Gishu - Eu acho que deve funcionar para esse fim. Você pode copiar e colar seu código para mostrar como está usando a chamada? Eu esperaria que fosse FindChild <ListBox> (Expander myExpanderName, "myListBoxName").
CrimsonX
3
@CrimsonX Acho que encontrei outro estojo de esquina. Eu estava tentando encontrar o PART_SubmenuPlaceholder no RibbonApplicationMenuItem, mas o código acima não estava funcionando. Para resolvê-lo, eu precisava adicionar o seguinte: if (name == ElementName) else {foundChild = FindChild (filho, nome) if (foundChild! = Null) break; }
kevindaub
6
Por favor, tenha cuidado, há um erro ou mais na resposta. Ele será interrompido assim que atingir um filho do tipo pesquisado. Eu acho que você deve considerar / priorizar outras respostas.
Eric Ouellet
2
Esse código é ótimo, mas não funcionará se você não estiver procurando por um tipo específico de elemento; por exemplo, se você passar FrameworkElementcomo T, ele retornará nulo assim que o primeiro loop terminar. então você precisará fazer algumas modificações.
Amir Oveisi
131

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).

Drew Noakes
fonte
6
Para que isso funcione, você não precisa necessariamente adicionar o "x:" ao atributo name.
Brian buck
3
Isso nem sempre parece funcionar. Eu tenho UserControls que são combinados programaticamente em grades aninhadas como o conteúdo de uma janela de propriedades. A resposta do CrimsonX funciona bem, no entanto.
Matt
4
Isso não vai funcionar para elementos dentro ItemControls, ListBoxes, etc.
Sorensen
67

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);
John Myczek
fonte
Como você recebe ou o que é myControl?
Demodave 26/10/19
21

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.

Tri Q Tran
fonte
2
Por meu post acima, há um pequeno erro de implementação em seu código: stackoverflow.com/questions/636383/wpf-ways-to-find-controls/...
CrimsonX
18

Minhas extensões para o código.

  • Sobrecargas adicionadas para encontrar um filho por tipo, por tipo e critério (predicado), encontrar todos os filhos do tipo que atendam aos critérios
  • o método FindChildren é um iterador, além de ser um método de extensão para DependencyObject
  • FindChildren também percorre subárvores lógicas. Veja a postagem de Josh Smith no link do blog.

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

Gishu
fonte
-1 Exatamente o que eu estava prestes a implementar (predicado, iterador e método de extensão), mas há um 404 no link de origem. Mudará para +1 se o código estiver incluído aqui ou o link de origem for corrigido!
Cod3monk3y
@ cod3monk3y - Git migração matou o link que parece :) Aqui vai .. code.google.com/p/gishu-util/source/browse/...
Gishu
18

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;
            }
        }
    }
UrbanEsc
fonte
3
Boa, mas garantir o controle é carregado de outra forma GetChildrenCount retornará 0.
Klaus Nji
@UrbanEsc, por que você lança childuma segunda vez? Se você é childTypedo tipo T, pode escrever dentro de if: yield return childType... não?
Massimiliano Kraus 26/10
@MassimilianoKraus Ei, desculpe pela resposta tardia, mas você está certo. Eu atribuo isso para mim reescrever esse trecho várias vezes, e, portanto, isso pode ser um fragmento de um cheque diferente
UrbanEsc
16

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);
   }
}
Philipp
fonte
5
Por convenção, eu esperaria que qualquer 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)
de Drew Noakes
@DrewNoakes, como você sugere que Philipp chame isso? Além disso, mesmo com essa expectativa, acho o código dele claro e claro de usar.
ANeves 15/09/14
1
@ ANeves, neste caso, eu chamaria isso 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 :)
Drew Noakes
16

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;
}
andresp
fonte
1
Se você passar esse método a 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.
Tim Pohlmann
1
Bem, esta é a partir de 5 anos atrás, então eu não sei mesmo se ele funciona mais :)
andresp
Eu acabei de mencionar isso, porque eu tropeçavam, e outra poderia muito bem;)
Tim Pohlmann
13

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>();
Nathan Phillips
fonte
9

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
fonte
9

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

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).

VB Guy
fonte
6

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.

El Zorko
fonte
3

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>}}}" 
Neeraj
fonte
2

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;
Tim Pohlmann
fonte
1

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.

Amir Oveisi
fonte
0

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;
    }
}
Drew Noakes
fonte
-5

Tente isto

<TextBlock x:Name="txtblock" FontSize="24" >Hai Welcom to this page
</TextBlock>

Código por trás

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
Jayasri
fonte