Найти все элементы управления в окне WPF по типу
Я ищу способ найти все элементы управления в Window по их типу,
например: найти все TextBoxes
найти все элементы управления, реализующие определенный интерфейс и т. д.
15 ответов
Это должно сделать свое дело
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
}
Это самый простой способ:
IEnumerable<myType> collection = control.Children.OfType<myType>();
где control - корневой элемент окна.
Я адаптировал ответ @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)
Используйте вспомогательные классы VisualTreeHelper
или же LogicalTreeHelper
в зависимости от того, какое дерево вас интересует. Они оба предоставляют методы для получения дочерних элементов (хотя синтаксис немного отличается). Я часто использую эти классы для нахождения первого вхождения определенного типа, но вы можете легко изменить его, чтобы найти все объекты этого типа:
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;
}
Я обнаружил, что строка VisualTreeHelper.GetChildrenCount(depObj);, используемая в нескольких приведенных выше примерах, не возвращает ненулевое количество для групповых ячеек, в частности, когда GroupBox содержит сетку, а сетка содержит дочерние элементы. Я полагаю, что это может быть потому, что 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 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;
}
}
Чтобы получить список всех дочерних элементов определенного типа, вы можете использовать:
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;
}
Небольшое изменение в рекурсии, чтобы вы могли, например, найти дочерний элемент управления вкладками элемента управления вкладками.
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;
}
Действительно хороший ответ.
Версия 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
Использование (это отключает все текстовые поля в окне):
For Each tb As TextBox In FindVisualChildren(Of TextBox)(Me)
tb.IsEnabled = False
Next
Моя версия для 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;
};
Обратите внимание, что использование VisualTreeHelper работает только с элементами управления, производными от Visual или Visual3D. Если вам также необходимо проверить другие элементы (например, TextBlock, FlowDocument и т. Д.), Использование VisualTreeHelper вызовет исключение.
Вот альтернатива, которая при необходимости возвращается к логическому дереву:
http://www.hardcodet.net/2009/06/finding-elements-in-wpf-tree-both-ways
И вот как это работает вверх
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 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);
По какой-то причине ни один из ответов, опубликованных здесь, не помог мне получить все элементы управления данного типа, содержащиеся в данном элементе управления в моем главном окне. Мне нужно было найти все пункты меню в одном меню, чтобы перебрать их. Все они не были прямыми потомками меню, поэтому мне удалось собрать только первые из них, используя любой из приведенного выше кода. Этот метод расширения является моим решением проблемы для всех, кто будет продолжать читать здесь.
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);
}
}
}
}
Надеюсь, поможет.
Я хотел добавить комментарий, но у меня меньше 50 баллов, поэтому я могу только "Ответить". Помните, что если вы используете метод "VisualTreeHelper" для извлечения объектов XAML "TextBlock", то он также будет захватывать объекты "кнопки" XAML. Если вы повторно инициализируете объект "TextBlock" путем записи в параметр Textblock.Text, вы больше не сможете изменять текст Button с помощью параметра Button.Content. Кнопка будет постоянно показывать текст, записанный в нее из действия записи Textblock.Text (с момента, когда он был извлечен -
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. XAML "TextBox" не собирается поиском "TextBlock".
Мне было проще без Visual Tree Helpers:
foreach (UIElement element in MainWindow.Children) {
if (element is TextBox) {
if ((element as TextBox).Text != "")
{
//Do something
}
}
};
В Обслуживаемом ответ возвращает обнаруженные элементы более или менее неупорядоченные, по итогам первого ребенка ветви как можно глубже, при этом обеспечивая обнаруженные элементы вдоль пути до возвратов и повторяя шаги для еще не разобранных веток дерев.
Если вам нужны дочерние элементы в порядке убывания, где сначала будут выданы прямые дочерние элементы, затем их дочерние элементы и так далее, следующий алгоритм будет работать:
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);