Wie finde ich WPF-Steuerelemente nach Name oder Typ?


264

Ich muss eine WPF-Steuerelementhierarchie nach Steuerelementen durchsuchen, die einem bestimmten Namen oder Typ entsprechen. Wie kann ich das machen?

Antworten:


311

Ich habe das von John Myczek und Tri Qs Algorithmus verwendete Vorlagenformat kombiniert, um einen findChild-Algorithmus zu erstellen, der für jedes übergeordnete Element verwendet werden kann. Denken Sie daran, dass das rekursive Durchsuchen eines Baums nach unten ein langwieriger Prozess sein kann. Ich habe dies nur in einer WPF-Anwendung vor Ort überprüft. Bitte kommentieren Sie eventuelle Fehler und ich werde meinen Code korrigieren.

WPF Snoop ist ein nützliches Werkzeug zum Betrachten des visuellen Baums. Ich würde dringend empfehlen, ihn beim Testen zu verwenden oder diesen Algorithmus zu verwenden, um Ihre Arbeit zu überprüfen.

Es gibt einen kleinen Fehler im Tri Q-Algorithmus. Nachdem das Kind gefunden wurde, können wir das korrekt gefundene Kind überschreiben, wenn Kinderzahl> 1 ist und wir erneut iterieren. Deshalb habe ich ein if (foundChild != null) break;in meinen Code eingefügt , um mit dieser Bedingung umzugehen.

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

Nennen Sie es so:

TextBox foundTextBox = 
   UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");

Hinweis Application.Current.MainWindowkann ein beliebiges übergeordnetes Fenster sein.


@ CrimsonX: Vielleicht mache ich das falsch ... Ich hatte ein ähnliches Bedürfnis, wo ich zu einem Steuerelement (ListBox) in einem ContentControl (Expander) gelangen musste. Der obige Code funktionierte bei mir nicht wie er ist. Ich musste den obigen Code aktualisieren, um festzustellen, ob ein Blattknoten (GetChildrenCount => 0) ein ContentControl ist. Wenn ja, prüfen Sie, ob der Inhalt den Kriterien für Name und Typ entspricht.
Gishu

@Gishu - Ich denke, es sollte für diesen Zweck funktionieren. Können Sie Ihren Code kopieren und einfügen, um zu zeigen, wie Sie den Anruf verwenden? Ich würde erwarten, dass es FindChild <ListBox> ist (Expander myExpanderName, "myListBoxName").
CrimsonX

3
@ CrimsonX Ich glaube, ich habe einen anderen Eckfall gefunden. Ich habe versucht, den PART_SubmenuPlaceholder im RibbonApplicationMenuItem zu finden, aber der obige Code funktionierte nicht. Um das Problem zu beheben, musste ich Folgendes hinzufügen: if (name == ElementName) else {foundChild = FindChild (Kind, Name) if (foundChild! = Null) break; }
Kevindaub

6
Bitte seien Sie vorsichtig, die Antwort enthält einen oder mehrere Fehler. Es wird angehalten, sobald es ein Kind des gesuchten Typs erreicht. Ich denke, Sie sollten andere Antworten berücksichtigen / priorisieren.
Eric Ouellet

2
Dieser Code ist großartig, funktioniert aber nicht, wenn Sie nicht nach einem bestimmten Elementtyp suchen. Wenn Sie beispielsweise FrameworkElementals T übergeben, wird er null zurückgeben, sobald die erste Schleife endet. Sie müssen also einige Änderungen vornehmen.
Amir Oveisi

131

Sie können ein Element auch mit FrameworkElement.FindName (Zeichenfolge) nach Namen suchen .

Gegeben:

<UserControl ...>
    <TextBlock x:Name="myTextBlock" />
</UserControl>

In die CodeBehind-Datei können Sie schreiben:

var myTextBlock = (TextBlock)this.FindName("myTextBlock");

Da es mit x: Name definiert ist, können Sie natürlich nur auf das generierte Feld verweisen, aber vielleicht möchten Sie es dynamisch und nicht statisch nachschlagen.

Dieser Ansatz ist auch für Vorlagen verfügbar, bei denen das benannte Element mehrmals angezeigt wird (einmal pro Verwendung der Vorlage).


6
Damit dies funktioniert, müssen Sie dem Namensattribut nicht unbedingt das "x:" hinzufügen.
Brian Buck

3
Dies scheint nicht immer zu funktionieren. Ich habe UserControls, die programmgesteuert in verschachtelten Gittern als Inhalt eines Eigenschaftenfensters kombiniert werden. Die Antwort von CrimsonX funktioniert jedoch einwandfrei.
Matt

4
Dies funktioniert nicht für Elemente in ItemControls, ListBoxes usw.
Sorensen

67

Sie können den VisualTreeHelper verwenden , um Steuerelemente zu finden. Im Folgenden finden Sie eine Methode, die mithilfe von VisualTreeHelper ein übergeordnetes Steuerelement eines angegebenen Typs findet. Sie können den VisualTreeHelper verwenden, um Steuerelemente auch auf andere Weise zu finden.

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

Nennen Sie es so:

Window owner = UIHelper.FindVisualParent<Window>(myControl);

Wie bekommst du oder was ist myControl?
Demodave

21

Ich wiederhole vielleicht nur alle anderen, aber ich habe einen hübschen Code, der die DependencyObject-Klasse mit einer Methode FindChild () erweitert, mit der Sie das Kind nach Typ und Name erhalten. Einfach einschließen und verwenden.

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

Ich hoffe, Sie finden es nützlich.


2
Gemäß meinem obigen Beitrag gibt es einen kleinen Implementierungsfehler in Ihrem Code: stackoverflow.com/questions/636383/wpf-ways-to-find-controls/…
CrimsonX

18

Meine Erweiterungen zum Code.

  • Überladungen hinzugefügt, um ein Kind nach Typ, Typ und Kriterien (Prädikat) zu finden. Finden Sie alle Kinder des Typs, die die Kriterien erfüllen
  • Die FindChildren-Methode ist nicht nur eine Erweiterungsmethode für DependencyObject, sondern auch ein Iterator
  • FindChildren geht auch logische Unterbäume. Siehe Josh Smiths Beitrag im Blog-Beitrag.

Quelle: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities

Erklärender Blog-Beitrag: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html


-1 Genau das, was ich implementieren wollte (Prädikat, Iterator und Erweiterungsmethode), aber der Quelllink enthält eine 404. Wird auf +1 geändert, wenn hier Code enthalten ist oder der Quelllink behoben ist!
cod3monk3y

@ cod3monk3y - Git Migration hat den Link getötet, wie es scheint :) Hier geht's .. code.google.com/p/gishu-util/source/browse/…
Gishu

18

Wenn Sie ALLE Steuerelemente eines bestimmten Typs finden möchten, könnte Sie auch dieses Snippet interessieren

    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
Gut, aber stellen Sie sicher, dass die Kontrolle geladen ist, sonst gibt GetChildrenCount 0 zurück.
Klaus Nji

@UrbanEsc, warum wirfst du childein zweites Mal? Wenn Sie childTypeTyp haben T, können Sie in das schreiben if: yield return childType... nein?
Massimiliano Kraus

@ MassimilianoKraus Hey, entschuldige die späte Antwort, aber du hast recht. Ich schreibe es mir zu, dass ich dieses Snippet mehrmals
umgeschrieben habe

16

Dadurch werden einige Elemente verworfen. Sie sollten es so erweitern, um eine größere Auswahl an Steuerelementen zu unterstützen. Eine kurze Diskussion finden Sie hier

 /// <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
Konventionell würde ich erwarten, dass jede Try*Methode zurückgibt boolund einen outParameter hat, der den fraglichen Typ zurückgibt, wie bei:bool IDictionary.TryGetValue(TKey key, out TValue value)
Drew Noakes

@DrewNoakes wie schlagen Sie Philipp dann vor, es zu nennen? Auch bei einer solchen Erwartung finde ich seinen Code sowohl klar als auch klar zu verwenden.
Aneves

1
@ ANeves, in diesem Fall würde ich es einfach nennen FindParent. Dieser Name impliziert für mich, dass er zurückkehren könnte null. Das Try*Präfix wird in der gesamten BCL wie oben beschrieben verwendet. Beachten Sie auch, dass die meisten anderen Antworten hier die Find*Namenskonvention verwenden. Es ist jedoch nur ein kleiner Punkt :)
Drew Noakes

16

Ich habe den Code von CrimsonX bearbeitet, da er nicht mit Typen der Oberklasse funktioniert:

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
Wenn Sie diese Methode übergeben DependencyObject, die keine ist FrameworkElement, kann dies eine Ausnahme auslösen. Auch die Verwendung GetChildrenCountbei jeder Iteration der forSchleife klingt nach einer schlechten Idee.
Tim Pohlmann

1
Nun

Ich habe es gerade erwähnt, weil ich darauf gestoßen bin und andere auch;)
Tim Pohlmann

13

Obwohl ich Rekursion im Allgemeinen liebe, ist sie beim Programmieren in C # nicht so effizient wie Iteration. Vielleicht ist die folgende Lösung besser als die von John Myczek vorgeschlagene? Dadurch wird eine Hierarchie aus einem bestimmten Steuerelement durchsucht, um ein Vorfahrensteuerelement eines bestimmten Typs zu finden.

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

Nennen Sie es so, um das WindowSteuerelement zu finden, das heißt ExampleTextBox:

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

9

Hier ist mein Code, um Steuerelemente nach Typ zu finden und gleichzeitig zu steuern, wie tief wir in die Hierarchie vordringen (maxDepth == 0 bedeutet unendlich tief).

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 ... Ich hatte ein Problem mit Ihrem Code, der nicht durch Benutzersteuerungen wiederkehrte. Es traf die Gitterwurzel und warf einen Fehler. Ich glaube, das behebt es für mich:

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

Ich habe eine Sequenzfunktion wie diese (die völlig allgemein ist):

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

Sofortige Kinder bekommen:

    public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
    {
        return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
            .Select(i => VisualTreeHelper.GetChild(obj, i));
    }

Alle Kinder im hiararchischen Baum finden:

    public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
    {
        return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
    }

Sie können dies im Fenster aufrufen, um alle Steuerelemente abzurufen.

Nachdem Sie die Sammlung haben, können Sie LINQ (dh OfType, Where) verwenden.


6

Da die Frage allgemein genug ist, um Menschen anzulocken, die nach Antworten auf sehr triviale Fälle suchen: Wenn Sie nur ein Kind und keinen Nachkommen wollen, können Sie Linq verwenden:

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

oder natürlich die offensichtliche Schleife, die über Kinder iteriert.


3

Diese Optionen sprechen bereits über das Durchlaufen des visuellen Baums in C #. Es ist möglich, den visuellen Baum auch in xaml mit der RelativeSource-Markup-Erweiterung zu durchlaufen. msdn

nach Typ finden

Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}" 

2

Hier ist eine Lösung, die ein flexibles Prädikat verwendet:

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

Sie können es zum Beispiel so nennen:

var child = FindChild(parent, child =>
{
    var textBlock = child as TextBlock;
    if (textBlock != null && textBlock.Name == "MyTextBlock")
        return true;
    else
        return false;
}) as TextBlock;

1

Dieser Code behebt nur den Fehler der @ CrimsonX-Antwort:

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

Sie müssen die Methode nur rekursiv weiter aufrufen, wenn die Typen übereinstimmen, die Namen jedoch nicht (dies geschieht, wenn Sie FrameworkElementals übergeben T). sonst wird es zurückkehren nullund das ist falsch.


0

Um einen Vorfahren eines bestimmten Typs aus dem Code zu finden, können Sie Folgendes verwenden:

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

Diese Implementierung verwendet eine Iteration anstelle einer Rekursion, die etwas schneller sein kann.

Wenn Sie C # 7 verwenden, kann dies etwas kürzer gemacht werden:

[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

Versuche dies

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

Code dahinter

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.