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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/636383/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:07:27  来源:igfitidea点击:

How can I find WPF controls by name or type?

wpfcontrolsfind

提问by alex2k8

I need to search a WPF control hierarchy for controls that match a given name or type. How can I do this?

我需要在 WPF 控件层次结构中搜索与给定名称或类型匹配的控件。我怎样才能做到这一点?

回答by CrimsonX

I combined the template format used by John Myczek and Tri Q's algorithm above to create a findChild Algorithm that can be used on any parent. Keep in mind that recursively searching a tree downwards could be a lengthy process. I've only spot-checked this on a WPF application, please comment on any errors you might find and I'll correct my code.

我结合了 John Myczek 使用的模板格式和上面的 Tri Q 算法来创建一个可以在任何父级上使用的 findChild 算法。请记住,向下递归搜索树可能是一个漫长的过程。我只在 WPF 应用程序上抽查过这个,请评论您可能发现的任何错误,我会更正我的代码。

WPF Snoopis a useful tool in looking at the visual tree - I'd strongly recommend using it while testing or using this algorithm to check your work.

WPF Snoop是查看可视化树的有用工具 - 我强烈建议在测试或使用此算法检查您的工作时使用它。

There is a small error in Tri Q's Algorithm.After the child is found, if childrenCount is > 1 and we iterate again we can overwrite the properly found child. Therefore I added a if (foundChild != null) break;into my code to deal with this condition.

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

Call it like this:

像这样调用它:

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

Note Application.Current.MainWindowcan be any parent window.

NoteApplication.Current.MainWindow可以是任何父窗口。

回答by Drew Noakes

You can also find an element by name using FrameworkElement.FindName(string).

您还可以使用FrameworkElement.FindName(string)按名称查找元素。

Given:

鉴于:

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

In the code-behind file, you could write:

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

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

Of course, because it's defined using x:Name, you could just reference the generated field, but perhaps you want to look it up dynamically rather than statically.

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

This approach is also available for templates, in which the named item appears multiple times (once per usage of the template).

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

回答by John Myczek

You can use the VisualTreeHelperto find controls. Below is a method that uses the VisualTreeHelper to find a parent control of a specified type. You can use the VisualTreeHelper to find controls in other ways as well.

您可以使用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);
      }
   }
}

Call it like this:

像这样调用它:

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

回答by Tri Q Tran

I may be just repeating everyone else but I do have a pretty piece of code that extends the DependencyObject class with a method FindChild() that will get you the child by type and name. Just include and use.

我可能只是在重复其他人,但我确实有一段漂亮的代码,它使用 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;
    }
}

Hope you find it useful.

希望你觉得它有用。

回答by Gishu

My extensions to the code.

我对代码的扩展。

  • Added overloads to find one child by type, by type and criteria (predicate), find all children of type which meet the criteria
  • the FindChildren method is an iterator in addition to being an extension method for DependencyObject
  • FindChildren walks logical sub-trees also. See Josh Smith's post linked in the blog post.
  • 添加了按类型、按类型和条件(谓词)查找一个子项的重载,查找所有符合条件的类型的子项
  • FindChildren 方法除了是 DependencyObject 的扩展方法之外,还是一个迭代器
  • FindChildren 也遍历逻辑子树。请参阅博客文章中链接的 Josh Smith 的文章。

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

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

Explanatory blog post : http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html

解释性博客文章:http: //madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html

回答by UrbanEsc

If you want to find ALL controls of a specific type, you might be interested in this snippet too

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

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

回答by andresp

I edited CrimsonX's code as it was not working with superclass types:

我编辑了 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;
}

回答by Philipp

This will dismiss some elements - you should extend it like this in order to support a wider array of controls. For a brief discussion, have a look 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);
   }
}

回答by Nathan Phillips

Whilst I love recursion in general, it's not as efficient as iteration when programming in C#, so perhaps the following solution is neater than the one suggested by John Myczek? This searches up a hierarchy from a given control to find an ancestor control of a particular type.

虽然我一般喜欢递归,但在 C# 编程时它不如迭代有效,所以也许下面的解决方案比 John Myczek 建议的解决方案更简洁?这将从给定的控件向上搜索层次结构以查找特定类型的祖先控件。

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

Call it like this to find the Windowcontaining a control called ExampleTextBox:

像这样调用它以查找Window包含名为 的控件ExampleTextBox

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

回答by Shawn Nelson

exciton80... I was having a problem with your code not recursing through usercontrols. It was hitting the Grid root and throwing an error. I believe this fixes it for me:

exciton80...我遇到了一个问题,你的代码不能通过用户控件递归。它正在击中 Grid 根并抛出错误。我相信这对我来说修复了它:

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