WPF:如何循环遍历窗口中的所有控件?

22

如何在WPF中循环遍历窗口中的所有控件?


2
这个链接可能是一个不错的起点:http://msdn.microsoft.com/en-us/library/bb613556.aspx - VVS
你可以在以下链接中找到答案: https://dev59.com/UXNA5IYBdhLWcg3wZ9DU - Barak Rosenfeld
7个回答

17

我在MSDN文档中找到了这个,所以它会有所帮助。

// Enumerate all the descendants of the visual object.
static public void EnumVisual(Visual myVisual)
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
    {
        // Retrieve child visual at specified index value.
        Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);

        // Do processing of the child visual object.

        // Enumerate children of the child visual object.
        EnumVisual(childVisual);
    }
}

在我看来,这个更简单。我用它来查找表单中的文本框并清除它们的数据。


我基于这个问题发布了另一个答案,它实际上返回一个 IEnumerable<Visual>,因此最终用户可以使用常规的 for 循环等。这使得枚举子集合非常简单,并允许通过 break; 等进行早期中止。请看一下。 - BrainSlugs83
当我尝试重现时,我的Visual(即.GetChildrenCount(myVisual)和.GetChild(myVisual,i))会在上出现错误,显示“无法将'Windows.UI.Composition.Visual'转换为'Windows.UI.Xaml.DependencyObject'”? WPF解决方案是否适用于UWP环境? 我是否使用了错误的using指令(Windows.UI.Composition)? - gaw

14

这种方法优于MSDN的方法,因为它是可重复使用的,并且允许早期退出循环(例如通过 break;等) - 它通过为每个迭代节省了一次方法调用来优化for循环 - 它还允许您使用常规的for循环遍历Visual的子元素,甚至是递归其子元素和其孙子元素 - 所以使用起来更加简单。

要使用它,您只需编写常规的foreach循环(甚至可以使用LINQ):

foreach (var ctrl in myWindow.GetChildren())
{
    // Process children here!
}

或者如果您不想递归:

foreach (var ctrl in myWindow.GetChildren(false))
{
    // Process children here!
}
为了让它起作用,你只需要将这个扩展方法放入任何静态类中,然后你就能随时像上面那样编写代码。

为了让它工作,您只需将此扩展方法放入任何静态类中,然后您随时可以编写类似上面的代码:

public static IEnumerable<Visual> GetChildren(this Visual parent, bool recurse = true)
{
    if (parent != null)
    {
        int count = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < count; i++)
        {
            // Retrieve child visual at specified index value.
            var child = VisualTreeHelper.GetChild(parent, i) as Visual;

            if (child != null)
            {
                yield return child;

                if (recurse)
                {
                    foreach (var grandChild in child.GetChildren(true))
                    {
                        yield return grandChild;
                    }
                }
            }
        }
    }
}

此外,如果您不喜欢默认启用递归,您可以更改扩展方法的声明,将recurse = false设置为默认行为。


7

获取控件所有子组件列表的类:

class Utility
    {
        private static StringBuilder sbListControls;

        public static StringBuilder GetVisualTreeInfo(Visual element)
        {
            if (element == null)
            {
                throw new ArgumentNullException(String.Format("Element {0} is null !", element.ToString()));
            }

            sbListControls = new StringBuilder();

            GetControlsList(element, 0);

            return sbListControls;
        }

        private static void GetControlsList(Visual control, int level)
        {
            const int indent = 4;
            int ChildNumber = VisualTreeHelper.GetChildrenCount(control);

            for (int i = 0; i <= ChildNumber - 1; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(control, i);

                sbListControls.Append(new string(' ', level * indent));
                sbListControls.Append(v.GetType());
                sbListControls.Append(Environment.NewLine);

                if (VisualTreeHelper.GetChildrenCount(v) > 0)
                {
                    GetControlsList(v, level + 1);
                }
            }
        }
    } 

2
遍历逻辑树不是比遍历视觉树更好、更有效吗? - Manvinder
sbListControls 应该是一个本地变量,以防多个线程同时调用这些方法。 - Roland Illig

1
我已使用以下方法获取所有控件。
    public static IList<Control> GetControls(this DependencyObject parent)
    {            
        var result = new List<Control>();
        for (int x = 0; x < VisualTreeHelper.GetChildrenCount(parent); x++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(parent, x);
            var instance = child as Control;

            if (null != instance)
                result.Add(instance);

            result.AddRange(child.GetControls());
        } 
        return result;
    }

0

我自己尝试了一下,找到了一个优雅的解决方案,而且适用于任何情况,不像这里发布的所有解决方案都是过度设计和有缺陷的。这里大部分答案都是过度设计和不稳定的。

思路是在Windows Presentation Foundation中循环遍历父控件,以获取所需类型或类型的子控件。

首先,您需要一个循环,最好将整数设置为零,并使用索引和VisualTreeHelper对象计算父控件内所有对象的数量作为循环条件。

  for(int ControlCounter = 0; ControlCounter <= VisualTreeHelper.GetChildrenCount(MaterialsContentComputerSystemsFoundationYear) - 1; ControlCounter++)
  {
          if(VisualTreeHelper.GetChild(MaterialsContentComputerSystemsFoundationYear, ControlCounter).GetType() == File1.GetType())
          {

                    Button b = (Button)VisualTreeHelper.GetChild(MaterialsContentComputerSystemsFoundationYear, ControlCounter);

                    if (ActualButtonControlIndex == App.FileIndex[ActualButtonControlIndex])
                    {
                      
                    }
                    else
                    {
                        b.Visibility = Visibility.Hidden;
                    }

                    ActualButtonControlIndex++;

                    System.Diagnostics.Debug.WriteLine(ActualButtonControlIndex + "  Button");
          }         
  }

在for循环中,您可以编写一个条件语句来验证当前索引处的控件类型是否与所需控件类型相同。在本例中,我使用了一个名为的控件,并且它是当前搜索的所需控件类型的一部分。您也可以使用存储按钮的变量进行类型比较。

  var b = new Button();

  for(int ControlCounter = 0; ControlCounter <= VisualTreeHelper.GetChildrenCount(MaterialsContentComputerSystemsFoundationYear) - 1; ControlCounter++)
  {
          if(VisualTreeHelper.GetChild(MaterialsContentComputerSystemsFoundationYear, ControlCounter).GetType() == b.GetType())
          {

                    Button B = (Button)VisualTreeHelper.GetChild(MaterialsContentComputerSystemsFoundationYear, ControlCounter);

                    if (ActualButtonControlIndex == App.FileIndex[ActualButtonControlIndex])
                    {
                      
                    }
                    else
                    {
                        B.Visibility = Visibility.Hidden;
                    }

                    ActualButtonControlIndex++;

                    System.Diagnostics.Debug.WriteLine(ActualButtonControlIndex + "  Button");
          }         
  }

在 for 循环的条件语句中,创建了一个所需控件类型的对象,并将其值设置为当前索引处 VisualTreeHelper 对象转换为 Button 类型的值。

您可以使用先前提到的按钮来设置应用程序窗口中父控件内当前索引处控件的大小、宽度、内容、颜色等属性。

  var b = new Button();

  for(int ControlCounter = 0; ControlCounter <= VisualTreeHelper.GetChildrenCount(MaterialsContentComputerSystemsFoundationYear) - 1; ControlCounter++)
  {
          if(VisualTreeHelper.GetChild(MaterialsContentComputerSystemsFoundationYear, ControlCounter).GetType() == b.GetType())
          {

                    Button B = (Button)VisualTreeHelper.GetChild(MaterialsContentComputerSystemsFoundationYear, ControlCounter);

                    if (ActualButtonControlIndex == App.FileIndex[ActualButtonControlIndex])
                    {
                        B.Content = "Hello";
                        B.FontSize = 20;
                        B.BackGround = new SolidColorBrush(Colors.Red);
                        
                    }
                    else
                    {
                        B.Visibility = Visibility.Hidden;
                    }

                    ActualButtonControlIndex++;

                    System.Diagnostics.Debug.WriteLine(ActualButtonControlIndex + "  Button");
          }         
  }

这个解决方案是模块化、简单、超稳定的,因此在任何情况下都非常有用。点个赞吧。


0

之前的答案都会返回由VisualTreeHelper.GetChildrenCountVisualTreeHelper.GetChild所识别的子项。然而,我发现对于TabControlTabItem及其内容不被视为子项。因此,这些将被省略,而原始问题(“窗口中的所有控件”)希望将它们包括在内。

要正确地循环遍历选项卡控件,您需要像这样做(修改自@BrainSlugs83的答案):

public static IEnumerable<Visual> GetChildren(this Visual parent, bool recurse = true)
{
    if (parent != null)
    {
        int count = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < count; i++)
        {
            // Retrieve child visual at specified index value.
            var child = VisualTreeHelper.GetChild(parent, i) as Visual;

            if (child != null)
            {
                yield return child;

                if (recurse)
                {
                    foreach (var grandChild in child.GetChildren(true))
                    {
                        yield return grandChild;
                    }
                    
                    // Tabs and their content are not picked up as visual children
                    if (child is TabControl childTab)
                    {
                        foreach (var childTabItem in childTab.Items)
                        {
                            yield return childTabItem;
                            foreach (var childTabItemChild in childTabItem.GetChildren(true))
                            {
                                yield return childTabItemChild;
                            }
                            if (childTabItem.Content != null && childTabItem.Content is Visual childTabItemContentAsVisual)
                            {
                                yield return childTabItemContentAsVisual;
                                foreach (var childTabItemGrandChild in childTabItemContentAsVisual.Children(true)
                                {
                                    yield return childTabItemGrandChild;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

或者,您可以遍历逻辑树而不是可视树:

public static IEnumerable<DependencyObject> GetLogicalChildren(this DependencyObject parent, bool recurse = true)
{
    if (parent == null) yield break;

    foreach (var child in LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>())
    {
        yield return child;
        if (recurse)
        {
            foreach (var grandChild in child.GetLogicalChildren(true))
            {
                yield return grandChild;
            }
        }
    }
}

0
一个微小的变化在MSDN的答案上...只需传入一个空的Visual对象列表,你的集合将被填充所有子视觉元素:
/// <summary>
/// Enumerate all the descendants (children) of a visual object.
/// </summary>
/// <param name="parent">Starting visual (parent).</param>
/// <param name="collection">Collection, into which is placed all of the descendant visuals.</param>
public static void EnumVisual(Visual parent, List<Visual> collection)
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        // Get the child visual at specified index value.
        Visual childVisual = (Visual)VisualTreeHelper.GetChild(parent, i);

        // Add the child visual object to the collection.
        collection.Add(childVisual);

        // Recursively enumerate children of the child visual object.
        EnumVisual(childVisual, collection);
    }
}

我基于你的答案发布了另一个答案,它实际上返回一个 IEnumerable<Visual>,这样最终用户就可以使用常规的 for 循环等。这节省了创建列表的开销,并允许通过 break; 等进行早期中止。看一下吧。 - BrainSlugs83

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接