如何按类型获取 WPF 容器的子级?

Posted

技术标签:

【中文标题】如何按类型获取 WPF 容器的子级?【英文标题】:How to get children of a WPF container by type? 【发布时间】:2012-05-03 23:29:37 【问题描述】:

如何在 WPF 中获取 MyContainer GridComboBox 类型的子控件?

<Grid x:Name="MyContainer">                    
    <Label Content="Name"  Name="label1"  />
    <Label Content="State" Name="label2"  />
    <ComboBox Height="23" HorizontalAlignment="Left" Name="comboBox1"/>
    <ComboBox Height="23" HorizontalAlignment="Left" Name="comboBox3" />
    <ComboBox Height="23" HorizontalAlignment="Left" Name="comboBox4" />
</Grid>

这行给了我一个错误:

var myCombobox = this.MyContainer.Children.GetType(ComboBox);

【问题讨论】:

【参考方案1】:

此扩展方法将递归搜索所需类型的子元素:

public static T GetChildOfType<T>(this DependencyObject depObj) 
    where T : DependencyObject

    if (depObj == null) return null;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
    
        var child = VisualTreeHelper.GetChild(depObj, i);

        var result = (child as T) ?? GetChildOfType<T>(child);
        if (result != null) return result;
    
    return null;

所以使用它你可以要求MyContainer.GetChildOfType&lt;ComboBox&gt;()

【讨论】:

LogicalTreeHelper.FindLogicalNode(DependencyObject depObj, string elementName) 帮助我实现了同样的目标。【参考方案2】:

Children 是 UIElement 的集合。因此,您需要遍历项目并确定每个项目是否属于所需类型。幸运的是,已经有一个 Linq 方法可以做到这一点,即Enumerable.OfType&lt;T&gt;,您可以使用Extension Method 语法方便地调用它:

var comboBoxes = this.MyContainer.Children.OfType<ComboBox>();

此方法根据集合的类型过滤集合,并在您的情况下仅返回 ComboBox 类型的元素。

如果您只想要第一个 ComboBox(正如您的变量名称所暗示的那样),您可以在查询中附加对 FirstOrDefault() 的调用:

var myComboBox = this.MyContainer.Children.OfType<ComboBox>().FirstOrDefault();

【讨论】:

这不会在 `ContentControl 中搜索【参考方案3】:

所有这些答案,但一个使用递归,IMO 只是蹩脚:)

获得视觉孩子:

public static IEnumerable<T> FindVisualChildren<T>([NotNull] this DependencyObject parent) where T : DependencyObject

    if (parent == null)
        throw new ArgumentNullException(nameof(parent));

    var queue = new Queue<DependencyObject>(new[] parent);

    while (queue.Any())
    
        var reference = queue.Dequeue();
        var count = VisualTreeHelper.GetChildrenCount(reference);

        for (var i = 0; i < count; i++)
        
            var child = VisualTreeHelper.GetChild(reference, i);
            if (child is T children)
                yield return children;

            queue.Enqueue(child);
        
    

获取逻辑子节点:

public static IEnumerable<T> FindLogicalChildren<T>([NotNull] this DependencyObject parent) where T : DependencyObject

    if (parent == null)
        throw new ArgumentNullException(nameof(parent));

    var queue = new Queue<DependencyObject>(new[] parent);

    while (queue.Any())
    
        var reference = queue.Dequeue();
        var children = LogicalTreeHelper.GetChildren(reference);
        var objects = children.OfType<DependencyObject>();

        foreach (var o in objects)
        
            if (o is T child)
                yield return child;

            queue.Enqueue(o);
        
    

请注意,这两个深度遍历树,如果您希望在第一次遇到时停止,则更改两个代码以包含对 queue.Enqueue 的调用在 else 块中。

【讨论】:

为什么递归是蹩脚的? @Konrad ***.com/questions/72209/recursion-or-iteration 我喜欢它看起来很优雅 @PiotrGolacki 谢谢! @Konrad 907 天后...? 递归很糟糕,因为你可以耗尽堆栈?【参考方案4】:

所有这些答案都非常好,但是,如果您试图找到类型 T 的 特定 视觉子级,您要么被困在全部获得它们然后找到你想要的那个,或者希望你得到的第一个是你想要的。我合并了几种方法以根据标准找到特定的方法。它有点像 LINQ,但我不想尝试处理递归枚举器。

像这样使用它:

MyContainer.FirstOrDefaultChild<Label>(l => l.Content=="State")

我把它写成一个扩展方法。

public static class DependencyObjectExtensions

    public static T FirstOrDefaultChild<T>(this DependencyObject parent, Func<T, bool> selector) 
        where T : DependencyObject
    
        T foundChild;
        return FirstOrDefaultVisualChildWhere(parent, selector, out foundChild) ? foundChild : default(T);
    

    private static bool FirstOrDefaultVisualChildWhere<T>(DependencyObject parent, Func<T, bool> selector,
        out T foundChild) where T : DependencyObject
    
        var count = VisualTreeHelper.GetChildrenCount(parent);
        for (var i = 0; i < count; i++)
        
            var child = VisualTreeHelper.GetChild(parent, i);
            var tChild = child as T;
            if (tChild != null)
            
                if (!selector(tChild)) continue;
                foundChild = tChild;
                return true;
            

            if (FirstOrDefaultVisualChildWhere(child, selector, out foundChild))
            
                return true;
            
        
        foundChild = default(T);
        return false;
    

【讨论】:

【参考方案5】:

搜索包含(屏幕的)预定点的特定类型的第一个子项:

(param 'point'是调用'PointToScreen'函数的结果(Visual类型声明))

private TDescendantType FindDescendant<TDescendantType>(DependencyObject parent, Point screenPoint) 
         where TDescendantType : DependencyObject

    int count = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < count; i++)
    
        var child = VisualTreeHelper.GetChild(parent, i);
        if (child is Visual)
        
            Point point = ((Visual)child).PointFromScreen(screenPoint);
            Rect rect = VisualTreeHelper.GetDescendantBounds((Visual)child);

            if (!rect.Contains(point))
                continue;
        

        if (child is TDescendantType)
        
            return (TDescendantType)child;
        

        child = FindDescendant<TDescendantType>(child, screenPoint);
        if (child != null)
        
            return (TDescendantType)child;
        
    
    return null;

【讨论】:

请edit 提供更多信息。纯代码和“试试这个”的答案是discouraged,因为它们不包含可搜索的内容,也没有解释为什么有人应该“试试这个”。我们在这里努力成为知识的资源。【参考方案6】:

我找到了这个工作示例:

foreach (object o in LogicalTreeHelper.GetChildren(myWindow))

    if (o is SomeTypeOfMine)
    
      //do something
    

来源:https://social.msdn.microsoft.com/Forums/vstudio/en-US/e0be708a-5fa2-4479-b5a0-8ff44a963803/find-all-child-controls-of-a-type?forum=wpf

【讨论】:

以上是关于如何按类型获取 WPF 容器的子级?的主要内容,如果未能解决你的问题,请参考以下文章

iOS 自定义容器 VC 和需要特定方向的子级

如何从 WPF 中的子控件中获取键盘?

在 EaselJS 中按名称定位多个阶段的子级

如何按值命名父元素的子元素? SQL 服务器

flexbox + bootstrap 4. 如何拉伸 .col-**-* 的子级?

如何从目标 VC 的子级为协议设置委托?