Как я могу найти элементы управления WPF по имени или типу?


264

Мне нужно искать иерархию элементов управления WPF для элементов управления, которые соответствуют заданному имени или типу. Как я могу это сделать?

Ответы:


311

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

WPF Snoop - полезный инструмент для просмотра визуального дерева - я настоятельно рекомендую использовать его во время тестирования или использовать этот алгоритм для проверки вашей работы.

В алгоритме Tri Q есть небольшая ошибка. После того, как дочерний элемент найден, если childrenCount> 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. Если да, проверьте, соответствует ли контент критериям name + type.
Гишу

@Gishu - я думаю, что это должно работать для этой цели. Можете ли вы скопировать и вставить свой код, чтобы показать, как вы используете вызов? Я ожидаю, что это должен быть FindChild <ListBox> (Expander myExpanderName, "myListBoxName").
CrimsonX

3
@CrimsonX Я думаю, что нашел другой угловой случай. Я пытался найти держатель PART_SubmenuPlace в RibbonApplicationMenuItem, но приведенный выше код не работал. Чтобы решить эту проблему, мне нужно было добавить следующее: if (name == ElementName) else {foundChild = FindChild (child, name) if (foundChild! = Null) break; }
kevindaub

6
Пожалуйста, будьте осторожны, в ответе есть ошибка или более. Он остановится, как только достигнет дочернего объекта искомого типа. Я думаю, что вы должны рассмотреть / расставить приоритеты для других ответов.
Эрик Оуэлле

2
Этот код хорош, но он не будет работать, если вы не ищете элемент определенного типа, например, если вы передадите FrameworkElementкак T, он вернет ноль, как только закончится первый цикл. так что вам нужно будет сделать некоторые изменения.
Амир Овеиси

131

Вы также можете найти элемент по имени, используя FrameworkElement.FindName (string) .

Дано:

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

В файле с выделенным кодом вы можете написать:

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

Конечно, поскольку он определен с использованием x: Name, вы можете просто ссылаться на сгенерированное поле, но, возможно, вы хотите искать его динамически, а не статически.

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


6
Чтобы это работало, необязательно добавлять «x:» к атрибуту name.
Брайан Бак

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?
Demodave

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)
Дрю Нокс

@DrewNoakes, что вы предлагаете Филиппу назвать так? Кроме того, даже с таким ожиданием я нахожу его код ясным и понятным для использования.
Ноябрь

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который не является, 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 ... У меня была проблема с вашим кодом, не повторяющимся через usercontrols. Он попадал в корень сетки и выдавал ошибку. Я считаю, что это исправляет это для меня:

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, Где).


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

или, конечно, очевидный цикл, повторяющийся над Children.


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.