代码之家  ›  专栏  ›  技术社区  ›  alex2k8

如何按名称或类型查找WPF控件?

  •  241
  • alex2k8  · 技术社区  · 15 年前

    我需要在WPF控件层次结构中搜索与给定名称或类型匹配的控件。我该怎么做?

    18 回复  |  直到 6 年前
        1
  •  282
  •   Chris home    9 年前

    我结合了上面John Myczek和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 可以是任何父窗口。

        2
  •  113
  •   Drew Noakes    15 年前

    还可以使用 FrameworkElement.FindName(string) .

    鉴于:

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

    在代码隐藏文件中,您可以编写:

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

    当然,因为它是用x:name定义的,所以您可以只引用生成的字段,但也许您希望动态而不是静态地查找它。

    这种方法也适用于模板,其中命名项多次出现(每次使用模板一次)。

        3
  •  62
  •   John Myczek    14 年前

    你可以使用 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
  •  19
  •   Tri Q Tran    15 年前

    我可能只是重复其他人,但我确实有一段很好的代码,用findchild()方法扩展DependencyObject类,该方法将按类型和名称为您获取子类。只是包含和使用。

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

    希望你觉得它有用。

        5
  •  17
  •   Tim Pohlmann    9 年前

    我对代码的扩展。

    • 添加了重载以按类型、类型和条件(谓词)查找一个子级,并查找符合条件的类型的所有子级
    • 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
  •  16
  •   UrbanEsc    9 年前

    如果要查找特定类型的所有控件,您可能也对该代码段感兴趣。

        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
  •  15
  •   andresp    14 年前

    我编辑了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
  •  14
  •   Philipp    15 年前

    这将忽略一些元素-您应该像这样扩展它,以便支持更广泛的控件数组。为了简短的讨论,请看一下 here

     /// <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);
       }
    }
    
        9
  •  11
  •   Nathan Phillips    6 年前

    虽然我喜欢递归,但在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>();
    
        10
  •  8
  •   exciton80    14 年前

    下面是我的代码,用于按类型查找控件,同时控制我们进入层次结构的深度 (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
  •  8
  •   Shawn Nelson    13 年前

    激子80…你的代码没有通过用户控件递归,我遇到了一个问题。它击中了网格根并抛出了一个错误。我相信这能帮我解决问题:

    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
  •  7
  •   VB Guy    10 年前

    我有一个这样的序列函数(它是完全通用的):

        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
  •  5
  •   El Zorko    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();
        }
    }
    

    当然,也有明显的for循环迭代子代。

        14
  •  2
  •   Tim Pohlmann    9 年前

    下面是一个使用灵活谓词的解决方案:

    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;
    
        15
  •  2
  •   Chris home    9 年前

    这些选项已经讨论了如何在C中遍历可视化树。 它可以在XAML中遍历可视化树,也可以使用RelativeSource标记扩展。 msdn

    按类型查找

    Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}" 
    
        16
  •  1
  •   Amir Oveisi    7 年前

    此代码只修复了@crimsonx answer的错误:

     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 这是错误的。

        17
  •  0
  •   Drew Noakes    7 年前

    要从代码中查找给定类型的祖先,可以使用:

    [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;
        }
    }
    
        18
  •  -4
  •   dhh    9 年前

    试试这个

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

    代码落后

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