Відповіді:
Я поєднав формат шаблону, який використовується Джон Міцеком та алгоритмом Tri Q вище, щоб створити алгоритм findChild, який можна використовувати в будь-якому з батьків. Майте на увазі, що рекурсивний пошук дерева вниз може бути тривалим процесом. Я перевірив це лише на додатку WPF, будь ласка, прокоментуйте будь-які помилки, які ви знайдете, і я виправлю свій код.
WPF Snoop є корисним інструментом для перегляду візуального дерева - я настійно рекомендую використовувати його під час тестування або використання цього алгоритму для перевірки вашої роботи.
В алгоритмі Tri Q є невелика помилка. Після того, як дитина буде знайдена, якщо childCount дорівнює> 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
як T, він поверне нуль, як тільки закінчиться перший цикл. тож вам знадобиться зробити деякі зміни.
Ви також можете знайти елемент за назвою за допомогою FrameworkElement.FindName (рядок) .
Подано:
<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;
}
}
Сподіваюся, вам здається корисним.
Мої розширення до коду.
Джерело: 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;
}
}
}
child
вдруге? Якщо у вас є childType
тип T
, ви можете написати всередині if
: yield return childType
... ні?
Це відхилить деякі елементи - вам слід розширити його так, щоб підтримувати ширший набір елементів управління. Для короткої дискусії подивіться тут
/// <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);
}
}
Try*
метод повернеться bool
і матиме out
параметр, який повертає розглянутий тип, як це стосується:bool IDictionary.TryGetValue(TKey key, out TValue value)
FindParent
. Це ім'я для мене означає, що воно може повернутися null
. Try*
Префікс використовується по всьому BCL в дорозі я описав вище. Також зауважте, що більшість інших відповідей тут використовується Find*
умовою іменування. Це лише незначний момент :)
Я відредагував код 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;
}
DependencyObject
передаєте цей метод a, що не є, FrameworkElement
він може кинути виняток. Також використання GetChildrenCount
для кожної ітерації for
циклу звучить як погана ідея.
Хоча я люблю рекурсію взагалі, вона не настільки ефективна, як ітерація при програмуванні на 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;
}
Назвіть це так, щоб знайти Window
елемент управління, який називається ExampleTextBox
:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Ось мій код, щоб знайти елементи управління від типу, контролюючи, наскільки глибоко ми йдемо в ієрархію (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 ... У мене виникла проблема з тим, що ваш код не повторювався через користувальницькі контролі. Це вдарило по Grid root і кинуло помилку. Я вважаю, що це виправляє це для мене:
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));
}
Пошук усіх дітей по ієрархічному дереву:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
{
return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
}
Ви можете зателефонувати цьому у Вікно, щоб отримати всі елементи керування.
Після отримання колекції ви можете використовувати LINQ (тобто OfType, Where).
Оскільки питання є досить загальним, що може залучати людей, які шукають відповіді на дуже тривіальні випадки: якщо ви просто хочете дитину, а не нащадка, ви можете використовувати 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();
}
}
або, звичайно, очевидний для ітерації циклу над дітьми.
Ці параметри вже говорять про обхід візуального дерева в C #. Можливо обхід візуального дерева також у xaml, використовуючи розширення розмітки RelativeSource. msdn
знайти за типом
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
Ось рішення, яке використовує гнучкий присудок:
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;
Цей код виправляє помилку відповіді @CrimsonX:
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;
}
}