Jawaban:
Saya menggabungkan format template yang digunakan oleh John Myczek dan algoritma Tri Q di atas untuk membuat Algoritma findChild yang dapat digunakan pada orangtua mana pun. Perlu diingat bahwa pencarian pohon secara rekursif ke bawah bisa menjadi proses yang panjang. Saya hanya memeriksa ini di aplikasi WPF, beri komentar tentang kesalahan yang mungkin Anda temukan dan saya akan memperbaiki kode saya.
WPF Snoop adalah alat yang berguna dalam melihat pohon visual - Saya sangat merekomendasikan menggunakannya saat menguji atau menggunakan algoritma ini untuk memeriksa pekerjaan Anda.
Ada kesalahan kecil dalam Algoritma Tri Q. Setelah anak ditemukan, jika childrenCount> 1 dan kita mengulangi lagi kita dapat menimpa anak yang ditemukan dengan benar. Karena itu saya menambahkan if (foundChild != null) break;
kode saya untuk menangani kondisi ini.
/// <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;
}
Sebut saja seperti ini:
TextBox foundTextBox =
UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");
Catatan Application.Current.MainWindow
dapat berupa jendela induk.
FrameworkElement
sebagai T, itu akan mengembalikan nol segera setelah loop pertama berakhir. jadi Anda perlu melakukan beberapa modifikasi.
Anda juga dapat menemukan elemen dengan nama menggunakan FrameworkElement.FindName (string) .
Diberikan:
<UserControl ...>
<TextBlock x:Name="myTextBlock" />
</UserControl>
Dalam file kode-belakang, Anda dapat menulis:
var myTextBlock = (TextBlock)this.FindName("myTextBlock");
Tentu saja, karena didefinisikan menggunakan x: Name, Anda bisa mereferensikan bidang yang dihasilkan, tetapi mungkin Anda ingin mencarinya secara dinamis daripada statis.
Pendekatan ini juga tersedia untuk template, di mana item bernama muncul beberapa kali (sekali per penggunaan template).
Anda dapat menggunakan VisualTreeHelper untuk menemukan kontrol. Di bawah ini adalah metode yang menggunakan VisualTreeHelper untuk menemukan kontrol induk dari tipe tertentu. Anda dapat menggunakan VisualTreeHelper untuk menemukan kontrol dengan cara lain juga.
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);
}
}
}
Sebut saja seperti ini:
Window owner = UIHelper.FindVisualParent<Window>(myControl);
Saya mungkin hanya mengulangi semua orang tetapi saya memiliki sepotong kode cantik yang memperluas kelas DependencyObject dengan metode FindChild () yang akan memberi Anda anak berdasarkan jenis dan nama. Cukup sertakan dan gunakan.
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;
}
}
Semoga bermanfaat.
Ekstensi saya ke kode.
Sumber: https://code.google.com/p/gishu-util/source/browse/#git%2FWPF%2FUtilities
Posting blog penjelasan: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html
Jika Anda ingin menemukan SEMUA kontrol dari jenis tertentu, Anda mungkin tertarik dengan cuplikan ini juga
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
yang kedua kalinya? Jika Anda memiliki childType
tipe T
, Anda dapat menulis di dalam if
: yield return childType
... tidak?
Ini akan mengabaikan beberapa elemen - Anda harus memperluasnya seperti ini untuk mendukung beragam kontrol. Untuk diskusi singkat, lihat di sini
/// <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*
metode untuk kembali bool
dan memiliki out
parameter yang kembali jenis yang bersangkutan, seperti:bool IDictionary.TryGetValue(TKey key, out TValue value)
FindParent
. Nama ini bagi saya menyiratkan bahwa itu bisa kembali null
. The Try*
prefix digunakan di seluruh BCL dengan cara saya jelaskan di atas. Perhatikan juga bahwa sebagian besar jawaban lain di sini menggunakan Find*
konvensi penamaan. Ini hanya poin kecil :)
Saya mengedit kode CrimsonX karena tidak bekerja dengan jenis superclass:
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
, ini bukan metode FrameworkElement
yang dapat membuang Pengecualian. Juga menggunakan GetChildrenCount
pada setiap iterasi dari for
loop terdengar seperti ide yang buruk.
Sementara saya suka rekursi secara umum, itu tidak seefisien iterasi ketika pemrograman dalam C #, jadi mungkin solusi berikut ini lebih rapi daripada yang disarankan oleh John Myczek? Ini mencari hierarki dari kontrol yang diberikan untuk menemukan kontrol leluhur dari jenis tertentu.
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;
}
Sebut saja seperti ini untuk menemukan Window
kontrol yang disebut ExampleTextBox
:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Inilah kode saya untuk menemukan kontrol berdasarkan Jenis sambil mengontrol seberapa dalam kita masuk ke hierarki (maxDepth == 0 berarti jauh lebih dalam).
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 ... Saya mengalami masalah dengan kode Anda tidak berulang melalui usercontrols. Itu mengenai root Grid dan melempar kesalahan. Saya percaya ini memperbaikinya untuk saya:
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();
}
Saya memiliki fungsi urutan seperti ini (yang sepenuhnya umum):
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)));
}
Mendapatkan anak langsung:
public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
{
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
.Select(i => VisualTreeHelper.GetChild(obj, i));
}
Menemukan semua anak di bawah pohon hiararkis:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
{
return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
}
Anda dapat memanggil ini di Window untuk mendapatkan semua kontrol.
Setelah Anda memiliki koleksi, Anda dapat menggunakan LINQ (yaitu OfType, Di mana).
Karena pertanyaannya cukup umum sehingga mungkin menarik orang mencari jawaban untuk kasus-kasus yang sangat sepele: jika Anda hanya ingin anak daripada keturunan, Anda dapat menggunakan 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();
}
}
atau tentu saja jelas untuk loop berulang atas Anak.
Opsi ini sudah berbicara tentang melintasi Pohon Visual di C #. Mungkin untuk melintasi pohon visual dalam xaml juga menggunakan ekstensi markup RelativeSource. msdn
temukan berdasarkan tipe
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
Berikut ini solusi yang menggunakan predikat fleksibel:
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;
}
Misalnya Anda bisa menyebutnya seperti ini:
var child = FindChild(parent, child =>
{
var textBlock = child as TextBlock;
if (textBlock != null && textBlock.Name == "MyTextBlock")
return true;
else
return false;
}) as TextBlock;
Kode ini hanya memperbaiki bug jawaban @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;
}
Anda hanya perlu terus memanggil metode secara rekursif jika jenisnya cocok tetapi nama tidak (ini terjadi ketika Anda lulus FrameworkElement
sebagai T
). kalau tidak itu akan kembali null
dan itu salah.
Untuk menemukan leluhur tipe tertentu dari kode, Anda dapat menggunakan:
[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;
}
}
Implementasi ini menggunakan iterasi alih-alih rekursi yang bisa sedikit lebih cepat.
Jika Anda menggunakan C # 7, ini bisa dibuat sedikit lebih pendek:
[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;
}
}