Come posso trovare i controlli WPF per nome o tipo?


264

Devo cercare una gerarchia di controlli WPF per i controlli che corrispondono a un determinato nome o tipo. Come posso fare questo?

Risposte:


311

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.MainWindowpuò essere qualsiasi finestra principale.


@CrimsonX: Forse sto sbagliando ... Avevo un'analoga necessità in cui dovevo raggiungere un controllo (ListBox) all'interno di un ContentControl (Expander). Il codice sopra non ha funzionato per me così com'è .. Ho dovuto aggiornare il codice sopra per vedere se un nodo foglia (GetChildrenCount => 0) è un ContentControl. Se sì, controlla se il contenuto corrisponde ai criteri nome + tipo.
Gishu,

@Gishu - Penso che dovrebbe funzionare a questo scopo. Puoi copiare e incollare il tuo codice per mostrare come stai usando la chiamata? Mi aspetto che dovrebbe essere FindChild <ListBox> (Expander myExpanderName, "myListBoxName").
CrimsonX,

3
@CrimsonX Penso di aver trovato un altro caso d'angolo. Stavo cercando di trovare PART_SubmenuPlaceholder in RibbonApplicationMenuItem, ma il codice sopra non funzionava. Per risolverlo, ho dovuto aggiungere quanto segue: if (name == ElementName) else {foundChild = FindChild (child, name) if (foundChild! = Null) break; }
kevindaub,

6
Si prega di fare attenzione, c'è un bug o più nella risposta. Si fermerà non appena raggiungerà un figlio del tipo cercato. Penso che dovresti considerare / dare priorità alle altre risposte.
Eric Ouellet,

2
Questo codice è ottimo, ma non funzionerà se non stai cercando un tipo specifico di elemento, ad esempio se passi FrameworkElementcome T, restituirà null non appena termina il primo ciclo. quindi dovrai apportare alcune modifiche.
Amir Oveisi,

131

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


6
Perché questo funzioni non è necessario aggiungere necessariamente "x:" all'attributo name.
Brian Buck

3
Questo non sembra funzionare sempre. Ho UserControls che sono combinati insieme a livello di codice in griglie nidificate come contenuto di una finestra delle proprietà. La risposta di CrimsonX funziona bene comunque.
Matt

4
Questo non funzionerà per gli elementi di ItemControls, ListBox, ecc.
Sorensen,

67

È 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);

Come si ottiene o cos'è myControl?
Demodave,

21

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.


2
Per il mio post di cui sopra, c'è un piccolo errore di implementazione nel codice: stackoverflow.com/questions/636383/wpf-ways-to-find-controls/...
CrimsonX

18

Le mie estensioni al codice.

  • Aggiunti sovraccarichi per trovare un figlio per tipo, per tipo e criteri (predicato), trovare tutti i figli di tipo che soddisfano i criteri
  • il metodo FindChildren è un iteratore oltre ad essere un metodo di estensione per DependencyObject
  • FindChildren cammina anche sottoalberi logici. Vedi il post di Josh Smith collegato nel post del blog.

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


-1 Esattamente quello che stavo per implementare (predicato, iteratore e metodo di estensione), ma c'è un 404 sul link di origine. Passerà a +1 se il codice è incluso qui o il collegamento alla fonte è stato corretto!
cod3monk3y

@ cod3monk3y - La migrazione Git ha ucciso il collegamento sembra :) Ecco qua .. code.google.com/p/gishu-util/source/browse/…
Gishu

18

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;
            }
        }
    }

3
Buono ma assicurati che il controllo sia caricato, altrimenti GetChildrenCount restituirà 0.
Klaus Nji,

@UrbanEsc, perché lanci childuna seconda volta? Se hai childTypedel tipo T, puoi scrivere all'interno di if: yield return childType... no?
Massimiliano Kraus,

@MassimilianoKraus Ehi, scusa per la risposta tardiva, ma hai ragione. Lo attribuisco a me riscrivere questo frammento più volte, e quindi questo potrebbe essere un frammento di un diverso controllo
UrbanEsc

16

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);
   }
}

5
Per convenzione, mi aspetterei che qualsiasi Try*metodo ritorni boole abbia un outparametro che restituisce il tipo in questione, come nel caso di:bool IDictionary.TryGetValue(TKey key, out TValue value)
Drew Noakes,

@DrewNoakes come suggerisci a Philipp di chiamarlo, allora? Inoltre, anche con una tale aspettativa, trovo il suo codice sia chiaro che chiaro da usare.
ANeves,

1
@ANeves, in questo caso lo chiamerei semplicemente 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ò :)
Drew Noakes,

16

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;
}

1
Se si passa questo metodo a DependencyObjectche non è un FrameworkElement, può generare un'eccezione. Anche l'utilizzo GetChildrenCountsu ogni iterazione del forloop sembra una cattiva idea.
Tim Pohlmann,

1
beh, questo è di 5 anni fa, quindi non so nemmeno se funziona più :)
andresp

L'ho appena menzionato, perché mi sono imbattuto in esso e anche altri potrebbero;)
Tim Pohlmann,

13

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 Windowcontrollo contenente un chiamato chiamato ExampleTextBox:

Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();

9

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();
    }
}

9

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();
}

8

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


6

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.


3

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>}}}" 

2

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;

1

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 FrameworkElementcome T). altrimenti tornerà nulle questo è sbagliato.


0

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;
    }
}

-5

Prova questo

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

Codice dietro

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.