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

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

Ответ 1

Я объединил формат шаблона, использованный Джоном Мичеком и алгоритмом 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 name is set for search
      if (frameworkElement != null && frameworkElement.Name == childName)
      {
        // if the child 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 может быть любым родительским окном.

Ответ 2

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

Дано:

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

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

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

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

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

Ответ 3

Вы можете использовать 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);

Ответ 4

Я могу просто повторять всех остальных, но у меня есть довольно кусочек кода, который расширяет класс 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 name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child name is of the request name
                        foundChild = child;
                        break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = child;
                    break;
                }
            }
        }
        return foundChild;
    }
}

Надеюсь, вы сочтете это полезным.

Ответ 5

Мои расширения кода.

  • Добавлены перегрузки для поиска одного дочернего типа по типу и критерию (предикат), найдите всех дочерних типов, соответствующих критериям
  • Метод 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

Ответ 6

Если вы хотите найти ВСЕ элементы управления определенного типа, вам может быть интересен этот фрагмент.

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

Ответ 7

Я редактировал код 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;
}

Ответ 8

Это отклонит некоторые элементы - вы должны расширить его таким образом, чтобы поддерживать более широкий массив элементов управления. Для краткого обсуждения посмотрите здесь

 /// <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 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 not a ContentElement, rely on VisualTreeHelper
     return VisualTreeHelper.GetParent(child);
   }
}

Ответ 9

Хотя мне нравится рекурсия в целом, она не так эффективна, как итерация при программировании на С#, поэтому, возможно, следующее решение является более аккуратным, чем тот, который предложил Джон Мойчек? Это ищет иерархию из определенного элемента управления, чтобы найти элемент управления предком определенного типа.

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

Ответ 10

Здесь мой код, чтобы найти элементы управления по типу, контролируя, насколько глубоко мы идем в иерархию (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();
    }
}

Ответ 11

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

Ответ 12

У меня есть такая функция последовательности (что полностью общая):

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

Ответ 13

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

или, конечно, очевидное для цикла, повторяющего "Дети".

Ответ 14

Эти параметры уже говорят об обходе визуального дерева в С#. Его можно также пересечь визуальное дерево в xaml, используя расширение разметки RelativeSource. msdn

найти по типу

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

Ответ 15

Вот решение, которое использует гибкий предикат:

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;

Ответ 16

Этот код исправляет ошибку @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 name is set for search
          if (frameworkElement != null && frameworkElement.Name == childName)
          {
            // if the child 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 и это неправильно.

Ответ 17

Чтобы найти предка данного типа из кода, вы можете использовать:

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

Эта реализация использует итерацию вместо рекурсии, которая может быть немного быстрее.

Если вы используете С# 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;
    }
}

Ответ 18

Попробуйте это

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

Код за

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