Как получить дочерний контейнер WPF по типу?
Как я могу получить дочерние элементы управления типа ComboBox
в MyContainer
Grid
в WPF?
<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);
5 ответов
Этот метод расширения будет рекурсивно искать дочерние элементы нужного типа:
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<ComboBox>()
,
Дети - это коллекция UIElements. Поэтому вам нужно перебрать элементы и определить для каждого элемента, имеет ли он требуемый тип. К счастью, для этого уже есть метод Linq, а именно Enumerable.OfType<T>
, который вы можете удобно вызвать, используя синтаксис метода расширения:
var comboBoxes = this.MyContainer.Children.OfType<ComboBox>();
Этот метод фильтрует коллекцию на основе их типа и возвращает, в вашем случае, только элементы типа ComboBox
,
Если вам нужен только первый ComboBox (как подсказывает имя вашей переменной), вы можете просто добавить вызов FirstOrDefault()
на запрос:
var myComboBox = this.MyContainer.Children.OfType<ComboBox>().FirstOrDefault();
Все эти ответы, кроме одного, используют рекурсию, 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
блок.
Все эти ответы очень хороши, но, если вы пытаетесь найти конкретного визуального потомка типа 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;
}
Поиск первого потомка определенного типа, который включает в себя заранее определенную точку (экрана):
(параметр 'point' является результатом вызова функции PointToScreen (объявленной в визуальном типе))
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;
}
Я нашел этот рабочий пример:
foreach (object o in LogicalTreeHelper.GetChildren(myWindow))
{
if (o is SomeTypeOfMine)
{
//do something
}
}