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

我正在寻找一种方法,通过它们的类型在窗口上找到所有控件,

例如:找到所有TextBoxes,找到所有实现特定接口的控件等。

212380 次浏览

使用helper类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;
}

这就是它向上的工作原理

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

这应该可以达到目的:

public static IEnumerable<T> FindVisualChilds<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) yield return (T)Enumerable.Empty<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject ithChild = VisualTreeHelper.GetChild(depObj, i);
if (ithChild == null) continue;
if (ithChild is T t) yield return t;
foreach (T childOfChild in FindVisualChilds<T>(ithChild)) yield return childOfChild;
}
}

然后像这样枚举控件

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

这是最简单的方法:

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

其中控件是窗口的根元素。

编辑-正如评论中指出的那样。这只是一个层次的深度。查看一个更深入的选项的公认答案。

请注意,使用VisualTreeHelper仅适用于派生自Visual或Visual3D的控件。如果你还需要检查其他元素(例如TextBlock, FlowDocument等),使用VisualTreeHelper将抛出一个异常。

如果有必要,这里有一个回到逻辑树的替代方案:

http://www.hardcodet.net/2009/06/finding-elements-in-wpf-tree-both-ways

要获得特定类型的所有子元素的列表,您可以使用:

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

我发现在上面几个例子中使用的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;
}
}
}
}

对递归做了一点小小的改变,例如,你可以找到一个标签控件的子标签控件。

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

我改编了@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) (也遵循了@noonand的建议&移除多余的子元素!= null)

下面是另一个紧凑的版本,使用泛型语法:

    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;
}
}
我想添加一个评论,但我只有不到50分,所以我只能“回答”。 注意,如果你使用“VisualTreeHelper”方法来检索XAML“TextBlock”对象,那么它也会抓取XAML“Button”对象。如果你重新初始化“TextBlock”对象写入TextBlock。参数,那么您将不再能够使用按钮更改按钮文本。参数内容。按钮将永久显示从文本块写入它的文本。文本写入操作(从检索时开始——

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“文本框”并添加方法(或事件)来模拟XAMAL按钮。XAML“TextBox”不是通过搜索“TextBlock”收集的。

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

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

我的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;
};
出于某种原因,没有一个答案张贴在这里帮助我得到所有给定类型的控件包含在我的主窗口中的给定控件。 我需要在一个菜单中找到所有的菜单项来迭代它们。它们并不都是菜单的直接后代,所以我使用上面的任何代码只收集了它们的第一行。 这个扩展方法是我解决这个问题的方法,任何人都将继续阅读到这里
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);
}
}
}
}

希望能有所帮助。

@Bryce,回答得很好。

VB。净版:

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

用法(这将禁用窗口中的所有文本框):

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

接受的答案通过尽可能深入地跟踪第一个子分支,返回或多或少无序的已发现元素,同时在返回并重复尚未解析的树枝的步骤之前,沿途生成已发现的元素。

如果你需要降序排列中的后代元素,其中直接子元素将首先产生,然后是它们的子元素,依此类推,以下算法将工作:

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

对于这个和更多的用例,你可以添加流动扩展方法到你的库:

 public static List<DependencyObject> FindAllChildren(this DependencyObject dpo, Predicate<DependencyObject> predicate)
{
var results = new List<DependencyObject>();
if (predicate == null)
return results;




for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dpo); i++)
{
var child = VisualTreeHelper.GetChild(dpo, i);
if (predicate(child))
results.Add(child);


var subChildren = child.FindAllChildren(predicate);
results.AddRange(subChildren);
}
return results;
}

举个例子:

 var children = dpObject.FindAllChildren(child => child is TextBox);