VisualTreeHelper使用——使用BFS实现高效率的视觉对象搜索
BFS,即广度优先搜索,是一种典型的图论算法。BFS算法与DFS(深度优先搜索)算法相对应,都是寻找图论中寻路的常用算法,两者的实现各有优点。
其中DFS算法常用递归实现,也就是常见的一条路找到黑再找另一条。如果我们要找的数据存储在一棵树最靠左侧的一边时,DFS的好处就显现出来了。无论数据在树的多深,DFS都能在线性的时间内找出这个数据。
而BFS算法常用队列实现,在查找树内对象时,会由树的根节点,一层一层向下找到目标。BFS的好处在于不管数据存在树的哪个节点,BFS都能以一个比较恒定的速度找到对象,这个速度几乎只与没寻找的深度和每层的节点数量相关。
所以在遍历视觉树时使用BFS相较于DFS有什么好处呢?
一个很明显的视觉树遍历特点就是,一般要找到的视觉对象都不会存储在离根节点特别远的地方。此时BFS相比DFS更不容易出现一条分支摸到黑啥都没找的情况。
话不多少了,上代码!
public static class VisualTreeExtension { /// <summary> /// Find all children T in dpObj using BFS. /// </summary> /// <param name="maxDepth">max finding depth of visual tree.</param> /// <param name="maxChildCount">maximum number of child nodes. Whose number of child nodes exceeds this value will be excluded</param> /// <returns></returns> public static IEnumerable<T> GetChild<T>(this DependencyObject dependencyObject, uint maxDepth = uint.MaxValue, uint maxChildCount = 20) where T : DependencyObject { int depth = 1; int count = VisualTreeHelper.GetChildrenCount(dependencyObject); Queue<DependencyObject> qObjs = new Queue<DependencyObject>(); for (int i = 0; i < count; i++) { qObjs.Enqueue(VisualTreeHelper.GetChild(dependencyObject, i)); } while (qObjs.Count != 0) { if (depth > maxDepth) yield break; depth++; count = qObjs.Count; for (int i = 0; i < count; i++) { var obj = qObjs.Dequeue(); if (obj is T result) yield return result; int child_count = VisualTreeHelper.GetChildrenCount(obj); if (child_count > maxChildCount) continue; for (int j = 0; j < child_count; j++) { qObjs.Enqueue(VisualTreeHelper.GetChild(obj, j)); } } } } /// <summary> /// Find all children T in dpObj using BFS. /// </summary> /// <param name="maxDepth">max finding depth of visual tree.</param> /// <param name="maxChildCount">maximum number of child nodes. Whose number of child nodes exceeds this value will be excluded</param> /// <returns></returns> public static IEnumerable<T> GetChild<T>(this DependencyObject dependencyObject, string name, uint maxDepth = uint.MaxValue, uint maxChildCount = 20) where T : FrameworkElement { int depth = 1; int count = VisualTreeHelper.GetChildrenCount(dependencyObject); Queue<DependencyObject> qObjs = new Queue<DependencyObject>(count); for (int i = 0; i < count; i++) { qObjs.Enqueue(VisualTreeHelper.GetChild(dependencyObject, i)); } while (qObjs.Count != 0) { if (depth > maxDepth) yield break; depth++; count = qObjs.Count; for (int i = 0; i < count; i++) { var obj = qObjs.Dequeue(); if (obj is T result && result.Name == name) yield return result; int child_count = VisualTreeHelper.GetChildrenCount(obj); if (child_count > maxChildCount) continue; for (int j = 0; j < child_count; j++) { qObjs.Enqueue(VisualTreeHelper.GetChild(obj, j)); } } } } }
使用方法:
var scrollViewer = listView.GetChild<ScrollViewer>().First();
也可以手动指定BFS的最大深度和最大子节点数量,避免遍历浪费太多时间