在WPF窗口中按类型查找所有控件


Answers:


430

这应该可以解决问题

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            if (child != null && child is T)
            {
                yield return (T)child;
            }

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

然后您像这样枚举控件

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
    // do something with tb here
}

68
注意:如果您试图使其工作并发现您的Window(例如)具有0个可视子项,请尝试在Loaded事件处理程序中运行此方法。如果您在构造函数中运行它(即使在InitializeComponent()之后),则可视子项尚未加载,并且将无法工作。
Ryan Lundy

24
从VisualTreeHelper切换到LogicalTreeHelpers也会导致包含不可见元素。
Mathias Lykkegaard Lorenzen 2012年

11
“ child!= null && child is T”行不是多余的吗?它不应该只读“孩子是T”

1
我将其变成一个扩展方法,只需插入一个thisbefore DependencyObject=>this DependencyObject depObj
Johannes Wanzek 2014年

1
@JohannesWanzek不要忘记你也需要改变,你把它放在孩子的位:的foreach(ChildofChild.FindVisualChildren <T>()){唧唧歪歪}
威尔

66

这是最简单的方法:

IEnumerable<myType> collection = control.Children.OfType<myType>(); 

其中control是窗口的根元素。


1
你是什​​么意思“根元素”?我应该写些什么来与我的主窗口表单建立联系?
deadfish 2011年

我明白了,在xaml视图中,我必须为网格设置名称<Grid Name="Anata_wa_yoru_o_shihai_suru_ai">here buttons</Grid>,然后才能使用Anata_wa_yoru_o_shihai_suru_ai.Children.OfType<myType>();
deadfish 2011年

68
这不能回答所提出的问题。它仅将子控件返回一级。
吉姆(Jim)

21

我改编了@Bryce Kahle的回答,以遵循@Mathias Lykkegaard Lorenzen的建议和使用LogicalTreeHelper

看起来工作还可以。;)

public static IEnumerable<T> FindLogicalChildren<T> ( DependencyObject depObj ) where T : DependencyObject
{
    if( depObj != null )
    {
        foreach( object rawChild in LogicalTreeHelper.GetChildren( depObj ) )
        {
            if( rawChild is DependencyObject )
            {
                DependencyObject child = (DependencyObject)rawChild;
                if( child is T )
                {
                    yield return (T)child;
                }

                foreach( T childOfChild in FindLogicalChildren<T>( child ) ) 
                {
                    yield return childOfChild;
                }
            }
        }
    }
}

(它仍然不会按照@Benjamin Berry和@David R分别提到的那样检查GroupBox中的选项卡控件或网格。)(也遵循@noonand的建议并删除了多余的孩子!= null)


一直在寻找如何清除我所有文本框的时间,我有多个选项卡,这是唯一起作用的代码:)谢谢
JohnChris

13

使用帮助程序类VisualTreeHelperLogicalTreeHelper取决于您感兴趣的。它们都提供了获取元素子级的方法(尽管语法略有不同)。我经常使用这些类来查找特定类型的首次出现,但是您可以轻松地对其进行修改以查找该类型的所有对象:

public static DependencyObject FindInVisualTreeDown(DependencyObject obj, Type type)
{
    if (obj != null)
    {
        if (obj.GetType() == type)
        {
            return obj;
        }

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject childReturn = FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type);
            if (childReturn != null)
            {
                return childReturn;
            }
        }
    }

    return null;
}

+1进行解释和发布,但是Bryce Kahle发布了可以正常运行的功能,谢谢
Andrija

这不能解决问题,而且泛型类型的答案也更加清晰。结合使用VisualTreeHelper.GetChildrenCount(obj)将解决此问题。但是,可以考虑将其作为一种选择。
Vasil Popov

9

我发现VisualTreeHelper.GetChildrenCount(depObj);在上面的几个示例中使用的行不会为GroupBoxes 返回非零计数,尤其是其中GroupBox包含Grid,以及Grid包含子元素的地方。我相信这可能是因为GroupBox不允许包含多个孩子,并且此孩子存储在其Content属性中。没有GroupBox.Children属性类型。我敢肯定我做的效率不是很高,但是我修改了该链中的第一个“ FindVisualChildren”示例,如下所示:

public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject 
{ 
    if (depObj != null) 
    {
        int depObjCount = VisualTreeHelper.GetChildrenCount(depObj); 
        for (int i = 0; i <depObjCount; i++) 
        { 
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i); 
            if (child != null && child is T) 
            { 
                yield return (T)child; 
            }

            if (child is GroupBox)
            {
                GroupBox gb = child as GroupBox;
                Object gpchild = gb.Content;
                if (gpchild is T)
                {
                    yield return (T)child; 
                    child = gpchild as T;
                }
            }

            foreach (T childOfChild in FindVisualChildren<T>(child)) 
            { 
                yield return childOfChild; 
            } 
        }
    }
} 

4

要获取特定类型的所有子项的列表,可以使用:

private static IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, Type type)
{
    if (obj != null)
    {
        if (obj.GetType() == type)
        {
            yield return obj;
        }

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
            {
                if (child != null)
                {
                    yield return child;
                }
            }
        }
    }

    yield break;
}

4

对递归进行小的更改,因此您可以例如找到选项卡控件的子选项卡控件。

    public static DependencyObject FindInVisualTreeDown(DependencyObject obj, Type type)
    {
        if (obj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);

                if (child.GetType() == type)
                {
                    return child;
                }

                DependencyObject childReturn = FindInVisualTreeDown(child, type);
                if (childReturn != null)
                {
                    return childReturn;
                }
            }
        }

        return null;
    }

3

这是又一个紧凑的版本,具有泛型语法:

    public static IEnumerable<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
    {
        if (obj != null) {
            if (obj is T)
                yield return obj as T;

            foreach (DependencyObject child in LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>()) 
                foreach (T c in FindLogicalChildren<T>(child)) 
                    yield return c;
        }
    }

2

这就是它向上工作的方式

    private T FindParent<T>(DependencyObject item, Type StopAt) where T : class
    {
        if (item is T)
        {
            return item as T;
        }
        else
        {
            DependencyObject _parent = VisualTreeHelper.GetParent(item);
            if (_parent == null)
            {
                return default(T);
            }
            else
            {
                Type _type = _parent.GetType();
                if (StopAt != null)
                {
                    if ((_type.IsSubclassOf(StopAt) == true) || (_type == StopAt))
                    {
                        return null;
                    }
                }

                if ((_type.IsSubclassOf(typeof(T)) == true) || (_type == typeof(T)))
                {
                    return _parent as T;
                }
                else
                {
                    return FindParent<T>(_parent, StopAt);
                }
            }
        }
    }


1

我想添加评论,但我的分数不到50分,因此我只能“回答”。请注意,如果您使用“ VisualTreeHelper”方法检索XAML“ TextBlock”对象,那么它还将获取XAML“ Button”对象。如果通过写入Textblock.Text参数来重新初始化“ TextBlock”对象,则将不再能够使用Button.Content参数来更改Button文本。Button将永久显示从Textblock中写入的文本。Text write操作(从检索到-

foreach (TextBlock tb in FindVisualChildren<TextBlock>(window))
{
// do something with tb here
   tb.Text = ""; //this will overwrite Button.Content and render the 
                 //Button.Content{set} permanently disabled.
}

要解决此问题,您可以尝试使用XAML“ TextBox”并添加方法(或事件)来模仿XAMAL Button。搜索“ TextBlock”不会收集XAML“ TextBox”。


那就是视觉树和逻辑树之间的区别。可视树包含每个控件(包括在控件模板中定义的控件组成的控件),而逻辑树仅包含实际控件(没有在模板中定义的控件)。此概念的可视化效果很好:链接
lauxjpn

1

我的C ++ / CLI版本

template < class T, class U >
bool Isinst(U u) 
{
    return dynamic_cast< T >(u) != nullptr;
}

template <typename T>
    T FindVisualChildByType(Windows::UI::Xaml::DependencyObject^ element, Platform::String^ name)
    {
        if (Isinst<T>(element) && dynamic_cast<Windows::UI::Xaml::FrameworkElement^>(element)->Name == name)
        {
            return dynamic_cast<T>(element);
        }
        int childcount = Windows::UI::Xaml::Media::VisualTreeHelper::GetChildrenCount(element);
        for (int i = 0; i < childcount; ++i)
        {
            auto childElement = FindVisualChildByType<T>(Windows::UI::Xaml::Media::VisualTreeHelper::GetChild(element, i), name);
            if (childElement != nullptr)
            {
                return childElement;
            }
        }
        return nullptr;
    };

1

由于某种原因,这里发布的答案都没有帮助我在MainWindow中获取给定控件中包含的所有给定类型的控件。我需要在一个菜单中找到所有菜单项以对其进行迭代。它们不是菜单的全部直接后代,因此我设法使用上面的任何代码仅收集了它们的第一个列表。对于任何将继续阅读全文的人,此扩展方法都是我解决该问题的方法。

public static void FindVisualChildren<T>(this ICollection<T> children, DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            var brethren = LogicalTreeHelper.GetChildren(depObj);
            var brethrenOfType = LogicalTreeHelper.GetChildren(depObj).OfType<T>();
            foreach (var childOfType in brethrenOfType)
            {
                children.Add(childOfType);
            }

            foreach (var rawChild in brethren)
            {
                if (rawChild is DependencyObject)
                {
                    var child = rawChild as DependencyObject;
                    FindVisualChildren<T>(children, child);
                }
            }
        }
    }

希望能帮助到你。


1

接受的答案返回发现的元素或多或少无序的,按照第一子分支尽可能深地,同时产生沿途发现的元素,回溯和重复的尚未解析树枝的步骤之前。

如果您需要按降序排列的后代元素,首先将产生直接子代,然后是其子代,依此类推,以下算法将起作用:

public static IEnumerable<T> GetVisualDescendants<T>(DependencyObject parent, bool applyTemplates = false)
    where T : DependencyObject
{
    if (parent == null || !(child is Visual || child is Visual3D))
        yield break;

    var descendants = new Queue<DependencyObject>();
    descendants.Enqueue(parent);

    while (descendants.Count > 0)
    {
        var currentDescendant = descendants.Dequeue();

        if (applyTemplates)
            (currentDescendant as FrameworkElement)?.ApplyTemplate();

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

            if (child is Visual || child is Visual3D)
                descendants.Enqueue(child);

            if (child is T foundObject)
                yield return foundObject;
        }
    }
}

生成的元素将按从最近到最远的顺序排序。这将很有用,例如,如果您正在寻找某种类型和条件的最近子元素:

var foundElement = GetDescendants<StackPanel>(someElement)
                       .FirstOrDefault(o => o.SomeProperty == SomeState);

1
缺少什么;child未定义。
codebender19年

1

@布莱斯,非常好的答案。

VB.NET版本:

Public Shared Iterator Function FindVisualChildren(Of T As DependencyObject)(depObj As DependencyObject) As IEnumerable(Of T)
    If depObj IsNot Nothing Then
        For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(depObj) - 1
            Dim child As DependencyObject = VisualTreeHelper.GetChild(depObj, i)
            If child IsNot Nothing AndAlso TypeOf child Is T Then
                Yield DirectCast(child, T)
            End If
            For Each childOfChild As T In FindVisualChildren(Of T)(child)
                Yield childOfChild
            Next
        Next
    End If
End Function

用法(这将禁用窗口中的所有TextBox):

        For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
          tb.IsEnabled = False
        Next

-1

我发现没有Visual Tree Helpers会更容易:

foreach (UIElement element in MainWindow.Children) {
    if (element is TextBox) { 
        if ((element as TextBox).Text != "")
        {
            //Do something
        }
    }
};

3
这只深入了一层。在XAML中,您具有深层嵌套的控件。
SQL Police
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.