Как я могу найти элементы управления WPF по имени или типу?
Мне нужно найти иерархию элементов управления WPF для элементов управления, которые соответствуют заданному имени или типу. Как я могу это сделать?
18 ответов:
Я объединил формат шаблона, используемый Джоном Микзеком и алгоритмом Tri Q выше, чтобы создать алгоритм findChild, который может быть использован на любом родителе. Имейте в виду, что рекурсивный поиск дерева вниз может быть длительным процессом. Я только проверил это в приложении WPF, прокомментируйте любые ошибки, которые вы можете найти, и я исправлю свой код.
WPF Snoop является полезным инструментом при просмотре визуального дерева - я настоятельно рекомендую использовать его во время тестирования или с помощью этого алгоритма проверить свою работу.
в алгоритме Tri Q есть небольшая ошибка. после того, как ребенок найден, если childrenCount > 1, и мы повторяем снова, мы можем перезаписать правильно найденный ребенок. Поэтому я добавил
if (foundChild != null) break;
в мой код, чтобы справиться с этим условием./// <summary> /// Finds a Child of a given item in the visual tree. /// </summary> /// <param name="parent">A direct parent of the queried item.</param> /// <typeparam name="T">The type of the queried item.</typeparam> /// <param name="childName">x:Name or Name of child. </param> /// <returns>The first parent item that matches the submitted type parameter. /// If not matching item can be found, /// a null parent is being returned.</returns> public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject { // Confirm parent and childName are valid. if (parent == null) return null; T foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); // If the child is not of the request child type child T childType = child as T; if (childType == null) { // recursively drill down the tree foundChild = FindChild<T>(child, childName); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // If the child's name is set for search if (frameworkElement != null && frameworkElement.Name == childName) { // if the child's name is of the request name foundChild = (T)child; break; } } else { // child element found. foundChild = (T)child; break; } } return foundChild; }
назовем это так:
TextBox foundTextBox = UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");
Примечание
Application.Current.MainWindow
может быть любое родительское окно.
вы также можете найти элемент по имени, используя FrameworkElement.Здесь(строка).
дано:
<UserControl ...> <TextBlock x:Name="myTextBlock" /> </UserControl>
в файле кода можно написать:
var myTextBlock = (TextBlock)this.FindName("myTextBlock");
конечно, поскольку он определен с помощью x: Name, вы можете просто ссылаться на созданное поле, но, возможно, вы хотите искать его динамически, а не статически.
этот подход также доступно для шаблонов, в которых именованный элемент появляется несколько раз (один раз за использование шаблона).
можно использовать VisualTreeHelper найти управление. Ниже приведен метод, который использует VisualTreeHelper для поиска родительского элемента управления указанного типа. VisualTreeHelper можно использовать и для поиска элементов управления другими способами.
public static class UIHelper { /// <summary> /// Finds a parent of a given item on the visual tree. /// </summary> /// <typeparam name="T">The type of the queried item.</typeparam> /// <param name="child">A direct or indirect child of the queried item.</param> /// <returns>The first parent item that matches the submitted type parameter. /// If not matching item can be found, a null reference is being returned.</returns> public static T FindVisualParent<T>(DependencyObject child) where T : DependencyObject { // get parent item DependencyObject parentObject = VisualTreeHelper.GetParent(child); // we’ve reached the end of the tree if (parentObject == null) return null; // check if the parent matches the type we’re looking for T parent = parentObject as T; if (parent != null) { return parent; } else { // use recursion to proceed with next level return FindVisualParent<T>(parentObject); } } }
назовем это так:
Window owner = UIHelper.FindVisualParent<Window>(myControl);
Я могу просто повторять все остальные, но у меня есть довольно кусок кода, который расширяет класс DependencyObject с помощью метода FindChild (), который даст вам ребенка по типу и имени. Просто включите и используйте.
public static class UIChildFinder { public static DependencyObject FindChild(this DependencyObject reference, string childName, Type childType) { DependencyObject foundChild = null; if (reference != null) { int childrenCount = VisualTreeHelper.GetChildrenCount(reference); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(reference, i); // If the child is not of the request child type child if (child.GetType() != childType) { // recursively drill down the tree foundChild = FindChild(child, childName, childType); } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // If the child's name is set for search if (frameworkElement != null && frameworkElement.Name == childName) { // if the child's name is of the request name foundChild = child; break; } } else { // child element found. foundChild = child; break; } } } return foundChild; } }
надеюсь, вы найдете его полезным.
мои расширения к коду.
- добавлены перегрузки, чтобы найти одного ребенка по типу, по типу и критериям (предикат), найти всех детей типа, которые соответствуют критериям
- метод FindChildren является итератором в дополнение к методу расширения для DependencyObject
- FindChildren ходит логические поддеревья также. См. сообщение Джоша Смита, связанное в блоге должность.
источник: 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
Если вы хотите найти все элементы управления определенного типа, вы можете быть заинтересованы в этом фрагменте тоже
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject { int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); var childType = child as T; if (childType != null) { yield return (T)child; } foreach (var other in FindVisualChildren<T>(child)) { yield return other; } } }
я редактировал код CrimsonX, так как он не работал с типами суперкласса:
public static T FindChild<T>(DependencyObject depObj, string childName) where T : DependencyObject { // Confirm obj is valid. if (depObj == null) return null; // success case if (depObj is T && ((FrameworkElement)depObj).Name == childName) return depObj as T; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) { DependencyObject child = VisualTreeHelper.GetChild(depObj, i); //DFS T obj = FindChild<T>(child, childName); if (obj != null) return obj; } return null; }
это отклонит некоторые элементы - вы должны расширить его таким образом, чтобы поддерживать более широкий спектр элементов управления. Для краткого обсуждения, посмотрите здесь
/// <summary> /// Helper methods for UI-related tasks. /// </summary> public static class UIHelper { /// <summary> /// Finds a parent of a given item on the visual tree. /// </summary> /// <typeparam name="T">The type of the queried item.</typeparam> /// <param name="child">A direct or indirect child of the /// queried item.</param> /// <returns>The first parent item that matches the submitted /// type parameter. If not matching item can be found, a null /// reference is being returned.</returns> public static T TryFindParent<T>(DependencyObject child) where T : DependencyObject { //get parent item DependencyObject parentObject = GetParentObject(child); //we've reached the end of the tree if (parentObject == null) return null; //check if the parent matches the type we're looking for T parent = parentObject as T; if (parent != null) { return parent; } else { //use recursion to proceed with next level return TryFindParent<T>(parentObject); } } /// <summary> /// This method is an alternative to WPF's /// <see cref="VisualTreeHelper.GetParent"/> method, which also /// supports content elements. Do note, that for content element, /// this method falls back to the logical tree of the element! /// </summary> /// <param name="child">The item to be processed.</param> /// <returns>The submitted item's parent, if available. Otherwise /// null.</returns> public static DependencyObject GetParentObject(DependencyObject child) { if (child == null) return null; ContentElement contentElement = child as ContentElement; if (contentElement != null) { DependencyObject parent = ContentOperations.GetParent(contentElement); if (parent != null) return parent; FrameworkContentElement fce = contentElement as FrameworkContentElement; return fce != null ? fce.Parent : null; } //if it's not a ContentElement, rely on VisualTreeHelper return VisualTreeHelper.GetParent(child); } }
хотя я люблю рекурсию в целом, она не так эффективна, как итерация при программировании на C#, поэтому, возможно, следующее решение более аккуратно, чем предложенное Джоном Микзеком выше?
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; }
вот мой код, чтобы найти элементы управления по типу, контролируя, как глубоко мы входим в иерархию (maxDepth = = 0 означает бесконечно глубоко).
public static class FrameworkElementExtension { 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; foreach (var c in (IEnumerable)o.GetType() .GetProperty(childrenProperty).GetValue(o, null)) { if (c.GetType().FullName == childType.FullName) list.Add(c); if (maxDepth == 0 || depth < maxDepth) list.AddRange(RecursiveFindControls( c, childType, depth + 1, maxDepth)); } } return list.ToArray(); } }
exciton80... У меня была проблема с ваш код не рекурсией через элементы управления UserControl. Он попал в корень сетки и бросил ошибку. Я считаю, что это исправляет его для меня:
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(); }
у меня есть функция последовательности, как это (который является полностью общим):
public static IEnumerable<T> SelectAllRecursively<T>(this IEnumerable<T> items, Func<T, IEnumerable<T>> func) { return (items ?? Enumerable.Empty<T>()).SelectMany(o => new[] { o }.Concat(SelectAllRecursively(func(o), func))); }
получение непосредственных детей:
public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj) { return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj)) .Select(i => VisualTreeHelper.GetChild(obj, i)); }
найти всех детей вниз по дереву hiararchical:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj) { return obj.FindChildren().SelectAllRecursively(o => o.FindChildren()); }
вы можете вызвать это в окне, чтобы получить все элементы управления.
после того, как у вас есть коллекция, вы можете использовать LINQ (т. е. OfType, где).
поскольку вопрос достаточно общий, он может привлечь людей, ищущих ответы на очень тривиальные случаи: если вы просто хотите ребенка, а не потомка, вы можете использовать Linq:
private void ItemsControlItem_Loaded(object sender, RoutedEventArgs e) { if (SomeCondition()) { var children = (sender as Panel).Children; var child = (from Control child in children where child.Name == "NameTextBox" select child).First(); child.Focus(); } }
или, конечно, очевидное для цикла итерации над детьми.
вот решение, которое использует гибкий предикат:
public static DependencyObject FindChild(DependencyObject parent, Func<DependencyObject, bool> predicate) { if (parent == null) return null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); if (predicate(child)) { return child; } else { var foundChild = FindChild(child, predicate); if (foundChild != null) return foundChild; } } return null; }
Вы можете, например, назвать так:
var child = FindChild(parent, child => { var textBlock = child as TextBlock; if (textBlock != null && textBlock.Name == "MyTextBlock") return true; else return false; }) as TextBlock;
эти параметры уже говорят о прохождении визуального дерева в C#. Его можно пересечь визуальное дерево в xaml, а также с помощью расширения разметки RelativeSource. msdn
найти по типу
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
этот код просто исправляет ошибку @CrimsonX answer:
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject { // Confirm parent and childName are valid. if (parent == null) return null; T foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); // If the child is not of the request child type child T childType = child as T; if (childType == null) { // recursively drill down the tree foundChild = FindChild<T>(child, childName); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // If the child's name is set for search if (frameworkElement != null && frameworkElement.Name == childName) { // if the child's name is of the request name foundChild = (T)child; break; } // recursively drill down the tree foundChild = FindChild<T>(child, childName); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; else { // child element found. foundChild = (T)child; break; } } return foundChild; }
вам просто нужно продолжать вызывать метод рекурсивно, если типы совпадают, но имена нет (это происходит, когда вы передаете
FrameworkElement
какT
). в противном случае он будет возвращатьnull
и это неправильно.
найти предка данного типа кода Вы можете использовать:
[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; } }