Як я можу знайти елементи керування WPF за назвою чи типом?


264

Мені потрібно шукати ієрархію управління WPF для елементів керування, які відповідають заданому імені або типу. Як я можу це зробити?

Відповіді:


311

Я поєднав формат шаблону, який використовується Джон Міцеком та алгоритмом Tri Q вище, щоб створити алгоритм findChild, який можна використовувати в будь-якому з батьків. Майте на увазі, що рекурсивний пошук дерева вниз може бути тривалим процесом. Я перевірив це лише на додатку WPF, будь ласка, прокоментуйте будь-які помилки, які ви знайдете, і я виправлю свій код.

WPF Snoop є корисним інструментом для перегляду візуального дерева - я настійно рекомендую використовувати його під час тестування або використання цього алгоритму для перевірки вашої роботи.

В алгоритмі Tri Q є невелика помилка. Після того, як дитина буде знайдена, якщо childCount дорівнює> 1, і ми повторимо повтор, ми можемо замінити правильно знайдену дитину. Тому я додав if (foundChild != null) break;у свій код, щоб вирішити цю умову.

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

Назвіть це так:

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

Примітка Application.Current.MainWindowможе бути будь-яким батьківським вікном.


@CrimsonX: Можливо, я роблю це неправильно ... У мене була подібна потреба, коли мені потрібно було потрапити до контролю (ListBox) всередині ContentControl (Expander). Наведений вище код не працював для мене, як є. Мені довелося оновити наведений вище код, щоб побачити, чи вузол листів (GetChildrenCount => 0) є ContentControl. Якщо так, перевірте, чи відповідає вміст критеріям назви + типу.
Gishu

@Gishu - Я думаю, що це має працювати для цієї мети. Чи можете ви скопіювати та вставити код, щоб показати, як ви використовуєте дзвінок? Я б очікував, що це має бути FindChild <ListBox> (Expander myExpanderName, "myListBoxName").
БагрянийX

3
@CrimsonX Я думаю, що я знайшов ще один кутовий випадок. Я намагався знайти PART_SubmenuPlaceholder у RibbonApplicationMenuItem, але код вище не працював. Щоб вирішити це, мені потрібно було додати наступне: якщо (name == ElementName) else {foundChild = FindChild (дитина, ім'я), якщо (foundChild! = Null) перерва; }
kevindaub

6
Будьте уважні, помилка чи більше у відповіді. Він припиниться, як тільки дістанеться до дитини пошукового типу. Думаю, вам слід розглянути / визначити пріоритети іншими відповідями.
Ерік Оуеллет

2
Цей код чудовий, але він не спрацює, якщо ви не шукаєте конкретний тип елемента, наприклад, якщо ви передасте FrameworkElementяк T, він поверне нуль, як тільки закінчиться перший цикл. тож вам знадобиться зробити деякі зміни.
Амір Овеїзі

131

Ви також можете знайти елемент за назвою за допомогою FrameworkElement.FindName (рядок) .

Подано:

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

У файлі з кодом ви можете написати:

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

Звичайно, оскільки це визначено за допомогою x: Name, ви можете просто посилатися на генероване поле, але, можливо, ви хочете переглянути його динамічно, а не статично.

Цей підхід також доступний для шаблонів, у яких названий елемент відображається кілька разів (один раз за використання шаблону).


6
Щоб це не працювало, вам не обов’язково додавати атрибут імені "x:".
Брайан Бак

3
Здається, це не завжди працює. У мене є UserControls, які об'єднані програмно разом у вкладені сітки як вміст вікна властивостей. Відповідь CrimsonX працює добре.
Метт

4
Це не спрацює з елементами в ItemControls, ListBoxes тощо
Sorensen

67

Ви можете використовувати VisualTreeHelper для пошуку елементів керування. Нижче наведено метод, який використовує VisualTreeHelper для пошуку батьківського контролю визначеного типу. Ви можете використовувати VisualTreeHelper, щоб знайти елементи керування і іншими способами.

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

Назвіть це так:

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

Як отримати або що таке myControl?
Демодав

21

Я, можливо, просто повторюю всіх інших, але у мене є гарний фрагмент коду, який розширює клас DependencyObject методом FindChild (), який надасть вам дитину за типом та іменем. Просто включіть і використовуйте.

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

Сподіваюся, вам здається корисним.


2
За мій пост вище, є невелика помилка реалізації в коді: stackoverflow.com/questions/636383/wpf-ways-to-find-controls / ...
CrimsonX

18

Мої розширення до коду.

  • Додано перевантаження, щоб знайти одну дитину за типом, за типом та критеріями (предикат), знайти всіх дітей типу, які відповідають критеріям
  • метод FindChildren є ітератором, крім того, що є методом розширення для DependencyObject
  • FindChildren також гуляє логічні під деревами. Дивіться публікацію Джоша Сміта, пов’язану в публікації в блозі.

Джерело: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities

Пояснювальна публікація в блозі: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html


-1 Саме те, що я збирався реалізувати (предикат, ітератор та метод розширення), але на джерелі посилання є 404. Зміниться на +1, якщо тут включений код або виправлено посилання на джерело!
cod3monk3y

@ cod3monk3y - міграція Git знищила посилання, здається :) Ось іди .. code.google.com/p/gishu-util/source/browse/…
Gishu

18

Якщо ви хочете знайти ВСІ елементи керування конкретного типу, вас може зацікавити і цей фрагмент

    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
Хороший, але переконайтеся, що контроль завантажений, інакше GetChildrenCount повернеться 0.
Клаус Нджі

@UrbanEsc, чому ти кидаєш childвдруге? Якщо у вас є childTypeтип T, ви можете написати всередині if: yield return childType... ні?
Массіміліано Краус

@MassimilianoKraus Ей, вибачте за пізню відповідь, але ви праві. Я приписую це переписуванню цього фрагмента кілька разів, і, таким чином, це може бути фрагмент іншої перевірки
UrbanEsc

16

Це відхилить деякі елементи - вам слід розширити його так, щоб підтримувати ширший набір елементів управління. Для короткої дискусії подивіться тут

 /// <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
За умовою, я б очікував, що будь-який Try*метод повернеться boolі матиме outпараметр, який повертає розглянутий тип, як це стосується:bool IDictionary.TryGetValue(TKey key, out TValue value)
Drew Noakes

@DrewNoakes, що ви пропонуєте Філіп зателефонувати? Крім того, навіть при такому очікуванні я вважаю його код зрозумілим і зрозумілим для використання.
ANeves

1
@ANeves, у цьому випадку я би просто назвав це FindParent. Це ім'я для мене означає, що воно може повернутися null. Try*Префікс використовується по всьому BCL в дорозі я описав вище. Також зауважте, що більшість інших відповідей тут використовується Find*умовою іменування. Це лише незначний момент :)
Дрю Ноакс

16

Я відредагував код CrimsonX, оскільки він не працював із типами суперкласу:

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
Якщо ви DependencyObjectпередаєте цей метод a, що не є, FrameworkElementвін може кинути виняток. Також використання GetChildrenCountдля кожної ітерації forциклу звучить як погана ідея.
Тім Польман

1
ну це з 5 років тому, тому я навіть не знаю, чи працює це більше :)
andresp

Я щойно згадував про це, бо натрапив на нього, і інші могли також;)
Тім Польман

13

Хоча я люблю рекурсію взагалі, вона не настільки ефективна, як ітерація при програмуванні на C #, тож, можливо, наступне рішення є більш охайним, ніж рішення, запропоноване Джоном Мицеком? Це здійснює пошук ієрархії із заданого елемента управління, щоб знайти контроль предків певного типу.

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

Назвіть це так, щоб знайти Windowелемент управління, який називається ExampleTextBox:

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

9

Ось мій код, щоб знайти елементи управління від типу, контролюючи, наскільки глибоко ми йдемо в ієрархію (maxDepth == 0 означає нескінченно глибокий).

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 ... У мене виникла проблема з тим, що ваш код не повторювався через користувальницькі контролі. Це вдарило по Grid root і кинуло помилку. Я вважаю, що це виправляє це для мене:

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

У мене така функція послідовності (яка є загальною):

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

Отримання негайних дітей:

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

Пошук усіх дітей по ієрархічному дереву:

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

Ви можете зателефонувати цьому у Вікно, щоб отримати всі елементи керування.

Після отримання колекції ви можете використовувати LINQ (тобто OfType, Where).


6

Оскільки питання є досить загальним, що може залучати людей, які шукають відповіді на дуже тривіальні випадки: якщо ви просто хочете дитину, а не нащадка, ви можете використовувати 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();
    }
}

або, звичайно, очевидний для ітерації циклу над дітьми.


3

Ці параметри вже говорять про обхід візуального дерева в C #. Можливо обхід візуального дерева також у xaml, використовуючи розширення розмітки RelativeSource. msdn

знайти за типом

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

2

Ось рішення, яке використовує гнучкий присудок:

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

Наприклад, ви можете назвати це так:

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

1

Цей код виправляє помилку відповіді @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;
    }  

Вам потрібно просто продовжити виклик методу рекурсивно, якщо типи відповідають, але імена не відповідають (це відбувається, коли ви передаєте FrameworkElementяк T). інакше повернеться, nullі це неправильно.


0

Щоб знайти предка даного типу з коду, ви можете використовувати:

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

Ця реалізація використовує ітерацію замість рекурсії, яка може бути дещо швидшою.

Якщо ви використовуєте C # 7, це може бути трохи коротше:

[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

Спробуйте це

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

Код позаду

var txtblock = sender as Textblock;
txtblock.Foreground = "Red"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.