Câu trả lời:
Tôi đã kết hợp định dạng mẫu được sử dụng bởi thuật toán của John Myczek và Tri Q ở trên để tạo ra Thuật toán findChild có thể được sử dụng cho bất kỳ phụ huynh nào. Hãy nhớ rằng việc tìm kiếm đệ quy một cây ở phía dưới có thể là một quá trình dài. Tôi chỉ kiểm tra tại chỗ trên ứng dụng WPF, vui lòng nhận xét về bất kỳ lỗi nào bạn có thể tìm thấy và tôi sẽ sửa mã của mình.
WPF Snoop là một công cụ hữu ích trong việc xem xét cây trực quan - tôi thực sự khuyên bạn nên sử dụng nó trong khi thử nghiệm hoặc sử dụng thuật toán này để kiểm tra công việc của bạn.
Có một lỗi nhỏ trong Thuật toán Tri Q. Sau khi tìm thấy đứa trẻ, nếu childrenCount> 1 và chúng ta lặp lại một lần nữa, chúng ta có thể ghi đè lên đứa trẻ được tìm thấy đúng. Vì vậy, tôi đã thêm một if (foundChild != null) break;
mã vào mã của tôi để đối phó với điều kiện này.
/// <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;
}
Gọi nó như thế này:
TextBox foundTextBox =
UIHelper.FindChild<TextBox>(Application.Current.MainWindow, "myTextBoxName");
Lưu ý Application.Current.MainWindow
có thể là bất kỳ cửa sổ cha mẹ.
FrameworkElement
T, nó sẽ trả về null ngay khi vòng lặp đầu tiên kết thúc. Vì vậy, bạn sẽ cần phải làm một số sửa đổi.
Bạn cũng có thể tìm thấy một phần tử theo tên bằng cách sử dụng FrameworkEuity.FindName (chuỗi) .
Được:
<UserControl ...>
<TextBlock x:Name="myTextBlock" />
</UserControl>
Trong tệp mã phía sau, bạn có thể viết:
var myTextBlock = (TextBlock)this.FindName("myTextBlock");
Tất nhiên, vì được xác định bằng x: Name, bạn chỉ có thể tham chiếu trường được tạo, nhưng có lẽ bạn muốn tìm kiếm nó một cách linh hoạt hơn là tĩnh.
Cách tiếp cận này cũng có sẵn cho các mẫu, trong đó mục được đặt tên xuất hiện nhiều lần (một lần cho mỗi lần sử dụng mẫu).
Bạn có thể sử dụng VisualTreeHelper để tìm các điều khiển. Dưới đây là phương pháp sử dụng VisualTreeHelper để tìm điều khiển chính của một loại được chỉ định. Bạn cũng có thể sử dụng VisualTreeHelper để tìm các điều khiển theo những cách khác.
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);
}
}
}
Gọi nó như thế này:
Window owner = UIHelper.FindVisualParent<Window>(myControl);
Tôi có thể chỉ đang lặp lại những người khác nhưng tôi có một đoạn mã đẹp mở rộng lớp DependencyObject bằng phương thức FindChild () sẽ đưa bạn đứa trẻ theo loại và tên. Chỉ cần bao gồm và sử dụng.
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;
}
}
Hi vọng bạn tìm được thứ hữu dụng.
Phần mở rộng của tôi để mã.
Nguồn: https://code.google.com.vn/p/gishu-util/source/browse/#git%2FWPF%2FUtilities
Bài đăng trên blog giải thích: http://madcoderspeak.blogspot.com/2010/04/wpf-find-child-control-of-specific-type.html
Nếu bạn muốn tìm TẤT CẢ các điều khiển của một loại cụ thể, bạn cũng có thể quan tâm đến đoạn trích này
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
lần thứ hai? Nếu bạn có childType
loại T
, bạn có thể viết bên trong if
: yield return childType
... không?
Điều này sẽ loại bỏ một số yếu tố - bạn nên mở rộng nó như thế này để hỗ trợ một loạt các điều khiển rộng hơn. Đối với một cuộc thảo luận ngắn gọn, hãy xem ở đây
/// <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*
phương thức nào trả về bool
và có một out
tham số trả về loại được đề cập, như với:bool IDictionary.TryGetValue(TKey key, out TValue value)
FindParent
. Tên này với tôi ngụ ý rằng nó có thể trở lại null
. Các Try*
tiền tố được sử dụng trong suốt BCL trong cách tôi mô tả ở trên. Cũng lưu ý rằng hầu hết các câu trả lời khác ở đây sử dụng Find*
quy ước đặt tên. Tuy nhiên, đó chỉ là một điểm nhỏ :)
Tôi đã chỉnh sửa mã của CrimsonX vì nó không hoạt động với các loại siêu lớp:
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
, nó không phải là FrameworkElement
ngoại lệ. Cũng sử dụng GetChildrenCount
trên mỗi lần lặp của for
vòng lặp nghe có vẻ là một ý tưởng tồi.
Mặc dù tôi thích đệ quy nói chung, nhưng nó không hiệu quả như lặp lại khi lập trình trong C #, vậy có lẽ giải pháp nào sau đây gọn gàng hơn so với đề xuất của John Myczek? Điều này tìm kiếm một hệ thống phân cấp từ một điều khiển nhất định để tìm một điều khiển tổ tiên của một loại cụ thể.
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;
}
Gọi nó như thế này để tìm Window
một điều khiển có chứa ExampleTextBox
:
Window window = ExampleTextBox.FindVisualAncestorOfType<Window>();
Đây là mã của tôi để tìm các điều khiển theo Loại trong khi kiểm soát mức độ chúng ta đi sâu vào cấu trúc phân cấp (maxDepth == 0 có nghĩa là sâu vô hạn).
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 ... Tôi gặp vấn đề với mã của bạn không được đệ quy thông qua các điều khiển người dùng. Nó đã nhấn vào gốc Grid và gây ra lỗi. Tôi tin rằng điều này sửa nó cho tôi:
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();
}
Tôi có một hàm chuỗi như thế này (hoàn toàn chung):
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)));
}
Bắt con ngay lập tức:
public static IEnumerable<DependencyObject> FindChildren(this DependencyObject obj)
{
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj))
.Select(i => VisualTreeHelper.GetChild(obj, i));
}
Tìm tất cả trẻ em xuống cây hiararchical:
public static IEnumerable<DependencyObject> FindAllChildren(this DependencyObject obj)
{
return obj.FindChildren().SelectAllRecursively(o => o.FindChildren());
}
Bạn có thể gọi nó trên Cửa sổ để có được tất cả các điều khiển.
Sau khi bạn có bộ sưu tập, bạn có thể sử dụng LINQ (ví dụ OfType, Where).
Vì câu hỏi đủ chung chung để nó có thể thu hút những người tìm kiếm câu trả lời cho những trường hợp rất tầm thường: nếu bạn chỉ muốn có một đứa trẻ chứ không phải là một hậu duệ, bạn có thể sử dụng 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();
}
}
hoặc tất nhiên là rõ ràng cho vòng lặp lặp trên trẻ em.
Các tùy chọn này đã nói về việc duyệt qua Visual Tree trong C #. Cũng có thể duyệt qua cây trực quan trong xaml bằng cách sử dụng phần mở rộng đánh dấu RelativeSource.msd
tìm theo loại
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type <TypeToFind>}}}"
Đây là một giải pháp sử dụng một vị từ linh hoạt:
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;
}
Ví dụ, bạn có thể gọi nó như thế này:
var child = FindChild(parent, child =>
{
var textBlock = child as TextBlock;
if (textBlock != null && textBlock.Name == "MyTextBlock")
return true;
else
return false;
}) as TextBlock;
Mã này chỉ sửa lỗi của câu trả lời @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;
}
Bạn chỉ cần tiếp tục gọi phương thức đệ quy nếu loại được phù hợp với nhưng tên không (điều này xảy ra khi bạn vượt qua FrameworkElement
như T
). nếu không nó sẽ trở lại null
và đó là sai.
Để tìm tổ tiên của một loại nhất định từ mã, bạn có thể sử dụng:
[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;
}
}
Việc thực hiện này sử dụng phép lặp thay vì đệ quy có thể nhanh hơn một chút.
Nếu bạn đang sử dụng C # 7, điều này có thể được thực hiện ngắn hơn một chút:
[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;
}
}