我需要在WPF控件层次结构中搜索与给定名称或类型匹配的控件。我该怎么做呢?
当前回答
exciton80……我有一个问题,你的代码不递归通过用户控件。它击中了网格根并抛出了一个错误。我相信这能解决我的问题:
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();
}
其他回答
这里有一些我经常使用的方法。
用法:
// Starts the search from thisUiElement (might be a UserContol, Window, etc..)
var combobox = thisUiElement.ChildOfType<ComboBox>();
var employeesListBox = thisUiElement.ChildOfName("EmployeesListBox");
// Starts the search from MainWindow to find the first DataGrid
var dataGrid = WpfUtils.ChildOfType<DataGrid>();
// Starts the search from MainWindow to find the all ListViews
List<ComboBox> allListViews = WpfUtils.ChildOfType<ListView>();
// Starts the search from MainWindow to find the element of name EmployeesComboBox
var combobox = WpfUtils.ChildOfName("EmployeesComboBox");
实现:
/*
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace WpfUtilities;
*/
public static class WpfUtils{
public static Window AppMainWindow =>
Application.Current?.MainWindow;
#region Find By Type
// Start the search from MainWindow, example usage: var combobox = WpfUtils.ChildOfType<ComboBox>();
public static T ChildOfType<T>() where T : DependencyObject =>
ChildOfType<T>(AppMainWindow);
/// This will return the first child of type T
public static T ChildOfType<T>(this DependencyObject parent)
where T : DependencyObject
{
if (parent == null) return null;
T child = default;
var numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < numVisuals; i++)
{
var v = VisualTreeHelper.GetChild(parent, i);
child = v as T ?? v.ChildOfType<T>();
if (child != null)
break;
}
return child;
}
// Start the search from MainWindow, example usage: List<ComboBox> comboboxes = WpfUtils.ChildOfType<ComboBox>();
public static IEnumerable<T> ChildrenOfType<T>() where T : DependencyObject =>
ChildrenOfType<T>(AppMainWindow);
/// This will not break the search when finding the first kid of type T, but it will keep searching to return all kids of type T
public static IEnumerable<T> ChildrenOfType<T>(
this DependencyObject parent) where T : DependencyObject
{
if (parent == null) yield break;
var numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < numVisuals; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T dependencyObject)
yield return dependencyObject;
foreach (var childOfChild in child.ChildrenOfType<T>())
yield return childOfChild;
}
}
#endregion
#region Find By Name
/// If parent is null, the search will start from MainWindow, example usage: var combobox = WpfUtils.ChildOfName("EmployeesCombobox");
public static FrameworkElement ChildOfName(string childName,
DependencyObject parent = null)
{
parent ??= AppMainWindow;
object child = null;
var numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < numVisuals; i++)
{
var v = VisualTreeHelper.GetChild(parent, i);
child = v is FrameworkElement f && f.Name == childName
? f
: ChildOfName(childName, v);
if (child != null)
break;
}
return child as FrameworkElement;
}
#endregion
#region
// Yet another useful method, if you are writing code in a .xaml.cs file and you want to get the parent of a type.. example usage: this.ParentOfType<Grid>(); this.ParentOfType<UserControl>(); this.ParentOfType<Window>();
public static T ParentOfType<T>(this DependencyObject child) where T : DependencyObject
{
var parentDepObj = child;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is T parent) return parent;
} while (parentDepObj != null);
return null;
}
#endregion
}
exciton80……我有一个问题,你的代码不递归通过用户控件。它击中了网格根并抛出了一个错误。我相信这能解决我的问题:
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();
}
虽然我一般喜欢递归,但在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;
}
像这样调用它来找到包含一个名为ExampleTextBox的控件的窗口:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
要从代码中找到给定类型的祖先,您可以使用:
[CanBeNull]
public static T FindAncestor<T>(DependencyObject d) where T : DependencyObject
{
while (true)
{
d = VisualTreeHelper.GetParent(d);
if (d == null)
return null;
var t = d as T;
if (t != null)
return t;
}
}
这个实现使用迭代而不是递归,可以稍微快一点。
如果你使用的是c# 7,这段代码可以略短:
[CanBeNull]
public static T FindAncestor<T>(DependencyObject d) where T : DependencyObject
{
while (true)
{
d = VisualTreeHelper.GetParent(d);
if (d == null)
return null;
if (d is T t)
return t;
}
}
对代码的扩展。
增加了重载,以按类型、按类型和标准(谓词)查找一个子元素,查找符合标准的所有类型的子元素 FindChildren方法除了是DependencyObject的扩展方法外,还是一个迭代器 FindChildren还遍历逻辑子树。请参阅博客中链接的Josh Smith的文章。
来源: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities
解释性博客文章: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html
推荐文章
- 在窗口上设置设计时间DataContext是一个编译器错误?
- 调用线程必须是STA,因为许多UI组件都要求这一点
- ContentControl和ContentPresenter有什么区别?
- WPF中控制模板和数据模板的区别
- 如何隐藏关闭按钮在WPF窗口?
- UI线程上的任务继续
- 我怎么能得到'查找'忽略。svn目录?
- WPF数据绑定:我如何访问“父”数据上下文?
- 从System.Drawing.Bitmap中加载WPF BitmapImage
- 我应该使用什么MVVM框架?
- 在WPF中引入一个窗口到前面
- Visual Studio单击“查找结果”在错误的窗口中打开代码
- 窗口高度=“自动”未按预期工作
- 我如何得到一个动画gif在WPF工作?
- ListBox和ListView的区别是什么