这篇博文主要是介绍了利用Linq API去查询WPF/Silverlight的可视化树。你可能在其他博客上看到了一些利用Linq查询可视化树的方法,但是我这里介绍的方法特殊在于它能够生成树状结构查询而不仅仅是简单的将可视化树展开。
我最近在codeproject发表了一篇文章,这篇文章介绍了一项生成Linq查询的树状结构的API的技术。利用了已经生成了的WPF/Silverlight API。如果你对更通用的方法或者是对这个API是怎么构造有兴趣,(或者对它怎么受XPath的影响的有兴趣)请移步到codeproject看看我的那篇文章。
这里我仅对Linq to Visual Tree API 做一个简洁的介绍。关于这个API的完整源代码在这篇文章的末尾。
Linq to Visual Tree API定义了很多DependencyObject的扩展方法,这些方法提供了跳转到别的DependencyObject实例的机制。我给出一些查询下列XAML标记的例子:
<Grid x:Name="GridOne" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBox x:Name="TextBoxOne" Text="One" Grid.Row="0" /> <StackPanel x:Name="StackPanelOne" Grid.Row="1"> <TextBox x:Name="TextBoxTwo" Text="Two" /> <Grid x:Name="GridTwo"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBox x:Name="TextBoxThree" Text="Three" Grid.Row="0" /> <StackPanel x:Name="StackPanelTwo" Grid.Row="1"> <TextBox x:Name="TextBoxFour" Text="Four"/> </StackPanel> </Grid> </StackPanel> </Grid>
我们开始一个简单的示例。首先引入Linq to Visual Tree的命名空间,然后使用Descendants方法去获取可视化树上一个控件实例的所有的子孙。
Descendants
using LinqToVisualTree; // all items within the visual tree IEnumerable<DependencyObject> allDescendants = this.Descendants(); /* gives ... 0 {Grid} [GridOne] 1 {TextBox} [TextBoxOne] 2 {StackPanel} [StackPanelOne] 3 {TextBox} [TextBoxTwo] 4 {Grid} [GridTwo] 5 {TextBox} [TextBoxThree] 6 {StackPanel} [StackPanelTwo] 7 {TextBox} [TextBoxFour] */ // all items within the visual tree of 'GridTwo' var descendantsOfGridTwo = GridTwo.Descendants(); /* gives ... 0 {TextBox} [TextBoxThree] 1 {StackPanel} [StackPanelTwo] 2 {TextBox} [TextBoxFour] */
每一个扩展方法也都有一个相应的带泛型参数的方法,该方法能够过滤集合去找寻特定类型的元素。
// all items within the visual tree of 'GridTwo' that are textboxes var textBoxDescendantsOfGridTwo = GridTwo.Descendants() .Where(i => i is TextBox); /* 0 {TextBox} [TextBoxThree] 1 {TextBox} [TextBoxFour] */ // a shorthand using the generic version of Descendants var textBoxDescendantsOfGridTwo2 = GridTwo.Descendants<TextBox>(); /* 0 {TextBox} [TextBoxThree] 1 {TextBox} [TextBoxFour] */
Elements
elements的扩展方法能获取在可视化树中的直接孩子:
// find all direct children of this user control var userControlChildren = this.Elements(); /* 0 {Grid} [GridOne] */ // find all direct children of the grid 'GridTwo' var gridChildren = GridTwo.Elements(); /* 0 {TextBox} [TextBoxThree] 1 {StackPanel} [StackPanelTwo] */ // find all direct children of the grid 'GridTwo' that are StackPanels var gridChildren2 = GridTwo.Elements<StackPanel>(); /* 0 {StackPanel} [StackPanelTwo] */
同样也有ElementBeforeSelf和ElementAfterSelf方法。它们分别返回该方法被调用后的Item的之前和之后elements。
Ancestors
这个ancestors扩展方法遍历整个可视化树直到树的根,找到所有的祖先节点:
// the ancestors for 'TextBoxFour' var ancestors = TextBoxFour.Ancestors(); /* 0 {StackPanel} [StackPanelTwo] 1 {Grid} [GridTwo] 2 {StackPanel} [StackPanelOne] 3 {Grid} [GridOne] 4 {MainPage} [] */ // the ancestors for 'TextBoxFour' that are StackPanels var stackPanelAncestors = TextBoxFour.Ancestors<StackPanel>(); /* 0 {StackPanel} [StackPanelTwo] 1 {StackPanel} [StackPanelOne] */
把这些方法混合到一起使用:
Linq to Tree API 不仅仅只是定义了依赖属性的扩展方法,对于IEnumerable<DependencyObject>同样适用。如果你先前有过使用Linq to XML的经验,我强烈建议你阅读我在codeproject上的文章,这样你就更好的理解它们是怎么工作的。
这个允许你形成更多复杂的查询。比如,你能够混合使用上诉扩展方法找出所有的带有Grid为直接孩子节点的TextBox:
var itemsFluent = this.Descendants<TextBox>() .Where(i => i.Ancestors().FirstOrDefault() is Grid); var itemsQuery = from v in this.Descendants<TextBox>() where v.Ancestors().FirstOrDefault() is Grid select v;
/* 0 {TextBox} [TextBoxOne] 1 {TextBox} [TextBoxThree] */
在这里你还能看到我们同时使用Linq的查询语法和扩展方法去实现相同的查询。在下例中查找出了所有的带有另一个孩子StackPanel的StackPanel:
var items2Fluent = this.Descendants<StackPanel>() .Descendants<StackPanel>(); var items2Query = from i in (from v in this.Descendants<StackPanel>() select v).Descendants<StackPanel>() select i;
/* 0 {StackPanel} [StackPanelTwo] */
最后,在这个例子中我们要以ASCII码输出整个可视化树!我们利用了DescendantsAndSelf,Ancestors和ElementsBeforeSelf方法,而且加上了比较流行的Linq Aggregate聚合方法。
string tree = this.DescendantsAndSelf().Aggregate("", (bc, n) => bc + n.Ancestors().Aggregate("", (ac, m) => (m.ElementsAfterSelf().Any() ? "| " : " ") + ac, ac => ac + (n.ElementsAfterSelf().Any() ? "+-" : "\\-")) + n.GetType().Name + "\n");
输出结果:
\-MainPage \-Grid +-TextBox | \-Grid | +-Border | | \-Grid | | +-Border | | \-Border | | \-ScrollViewer | | \-Border | | \-Grid | | +-ScrollContentPresenter | | | \-TextBoxView | | +-Rectangle | | +-ScrollBar | | \-ScrollBar | +-Border | +-Border | \-Border | \-Grid | +-Path | \-Path \-StackPanel +-TextBox | \-Grid | +-Border | | \-Grid | | +-Border | | \-Border | | \-ScrollViewer | | \-Border | | \-Grid | | +-ScrollContentPresenter | | | \-TextBoxView | | +-Rectangle | | +-ScrollBar | | \-ScrollBar | +-Border | +-Border | \-Border | \-Grid | +-Path | \-Path \-Grid +-TextBox | \-Grid | +-Border | | \-Grid | | +-Border | | \-Border | | \-ScrollViewer | | \-Border | | \-Grid | | +-ScrollContentPresenter | | | \-TextBoxView | | +-Rectangle | | +-ScrollBar | | \-ScrollBar | +-Border | +-Border | \-Border | \-Grid | +-Path | \-Path \-StackPanel \-TextBox \-Grid +-Border | \-Grid | +-Border | \-Border | \-ScrollViewer | \-Border | \-Grid | +-ScrollContentPresenter | | \-TextBoxView | +-Rectangle | +-ScrollBar | \-ScrollBar +-Border +-Border \-Border \-Grid +-Path \-Path
Note:这里的这段代码是在LayoutUpdated事件触发后执行的。这样我们不仅能够得到XAML中的elements,同样也能得到在模板中创建的elements。最后返回的是一个运行时可视化树。
你可以下载一个简单的包含上述所有示例的Silverlight程序:LinqToTree.zip
或者,你只是想要Linq to VisualTree 的源码,你可以拷贝下面的代码,它包含了整个API。包含了前面举例的所有方法,也包括了它们的IEnumerable等价方法和一些没有提及的东西
using System; using System.Linq; using System.Collections.Generic; using System.Windows; using System.Windows.Media; namespace LinqToVisualTree { /// <summary> /// Adapts a DependencyObject to provide methods required for generate /// a Linq To Tree API /// </summary> public class VisualTreeAdapter : ILinqTree<DependencyObject> { private DependencyObject _item; public VisualTreeAdapter(DependencyObject item) { _item = item; } public IEnumerable<DependencyObject> Children() { int childrenCount = VisualTreeHelper.GetChildrenCount(_item); for (int i = 0; i < childrenCount; i++) { yield return VisualTreeHelper.GetChild(_item, i); } } public DependencyObject Parent { get { return VisualTreeHelper.GetParent(_item); } } } } namespace LinqToVisualTree { /// <summary> /// Defines an interface that must be implemented to generate the LinqToTree methods /// </summary> /// <typeparam name="T"></typeparam> public interface ILinqTree<T> { IEnumerable<T> Children(); T Parent { get; } } public static class TreeExtensions { /// <summary> /// Returns a collection of descendant elements. /// </summary> public static IEnumerable<DependencyObject> Descendants(this DependencyObject item) { ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); foreach (var child in adapter.Children()) { yield return child; foreach (var grandChild in child.Descendants()) { yield return grandChild; } } } /// <summary> /// Returns a collection containing this element and all descendant elements. /// </summary> public static IEnumerable<DependencyObject> DescendantsAndSelf(this DependencyObject item) { yield return item; foreach (var child in item.Descendants()) { yield return child; } } /// <summary> /// Returns a collection of ancestor elements. /// </summary> public static IEnumerable<DependencyObject> Ancestors(this DependencyObject item) { ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); var parent = adapter.Parent; while (parent != null) { yield return parent; adapter = new VisualTreeAdapter(parent); parent = adapter.Parent; } } /// <summary> /// Returns a collection containing this element and all ancestor elements. /// </summary> public static IEnumerable<DependencyObject> AncestorsAndSelf(this DependencyObject item) { yield return item; foreach (var ancestor in item.Ancestors()) { yield return ancestor; } } /// <summary> /// Returns a collection of child elements. /// </summary> public static IEnumerable<DependencyObject> Elements(this DependencyObject item) { ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); foreach (var child in adapter.Children()) { yield return child; } } /// <summary> /// Returns a collection of the sibling elements before this node, in document order. /// </summary> public static IEnumerable<DependencyObject> ElementsBeforeSelf(this DependencyObject item) { if (item.Ancestors().FirstOrDefault() == null) yield break; foreach (var child in item.Ancestors().First().Elements()) { if (child.Equals(item)) break; yield return child; } } /// <summary> /// Returns a collection of the after elements after this node, in document order. /// </summary> public static IEnumerable<DependencyObject> ElementsAfterSelf(this DependencyObject item) { if (item.Ancestors().FirstOrDefault() == null) yield break; bool afterSelf = false; foreach (var child in item.Ancestors().First().Elements()) { if (afterSelf) yield return child; if (child.Equals(item)) afterSelf = true; } } /// <summary> /// Returns a collection containing this element and all child elements. /// </summary> public static IEnumerable<DependencyObject> ElementsAndSelf(this DependencyObject item) { yield return item; foreach (var child in item.Elements()) { yield return child; } } /// <summary> /// Returns a collection of descendant elements which match the given type. /// </summary> public static IEnumerable<DependencyObject> Descendants<T>(this DependencyObject item) { return item.Descendants().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection of the sibling elements before this node, in document order /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> ElementsBeforeSelf<T>(this DependencyObject item) { return item.ElementsBeforeSelf().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection of the after elements after this node, in document order /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> ElementsAfterSelf<T>(this DependencyObject item) { return item.ElementsAfterSelf().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection containing this element and all descendant elements /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this DependencyObject item) { return item.DescendantsAndSelf().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection of ancestor elements which match the given type. /// </summary> public static IEnumerable<DependencyObject> Ancestors<T>(this DependencyObject item) { return item.Ancestors().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection containing this element and all ancestor elements /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this DependencyObject item) { return item.AncestorsAndSelf().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection of child elements which match the given type. /// </summary> public static IEnumerable<DependencyObject> Elements<T>(this DependencyObject item) { return item.Elements().Where(i => i is T).Cast<DependencyObject>(); } /// <summary> /// Returns a collection containing this element and all child elements. /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this DependencyObject item) { return item.ElementsAndSelf().Where(i => i is T).Cast<DependencyObject>(); } } public static class EnumerableTreeExtensions { /// <summary> /// Applies the given function to each of the items in the supplied /// IEnumerable. /// </summary> private static IEnumerable<DependencyObject> DrillDown(this IEnumerable<DependencyObject> items, Func<DependencyObject, IEnumerable<DependencyObject>> function) { foreach (var item in items) { foreach (var itemChild in function(item)) { yield return itemChild; } } } /// <summary> /// Applies the given function to each of the items in the supplied /// IEnumerable, which match the given type. /// </summary> public static IEnumerable<DependencyObject> DrillDown<T>(this IEnumerable<DependencyObject> items, Func<DependencyObject, IEnumerable<DependencyObject>> function) where T : DependencyObject { foreach (var item in items) { foreach (var itemChild in function(item)) { if (itemChild is T) { yield return (T)itemChild; } } } } /// <summary> /// Returns a collection of descendant elements. /// </summary> public static IEnumerable<DependencyObject> Descendants(this IEnumerable<DependencyObject> items) { return items.DrillDown(i => i.Descendants()); } /// <summary> /// Returns a collection containing this element and all descendant elements. /// </summary> public static IEnumerable<DependencyObject> DescendantsAndSelf(this IEnumerable<DependencyObject> items) { return items.DrillDown(i => i.DescendantsAndSelf()); } /// <summary> /// Returns a collection of ancestor elements. /// </summary> public static IEnumerable<DependencyObject> Ancestors(this IEnumerable<DependencyObject> items) { return items.DrillDown(i => i.Ancestors()); } /// <summary> /// Returns a collection containing this element and all ancestor elements. /// </summary> public static IEnumerable<DependencyObject> AncestorsAndSelf(this IEnumerable<DependencyObject> items) { return items.DrillDown(i => i.AncestorsAndSelf()); } /// <summary> /// Returns a collection of child elements. /// </summary> public static IEnumerable<DependencyObject> Elements(this IEnumerable<DependencyObject> items) { return items.DrillDown(i => i.Elements()); } /// <summary> /// Returns a collection containing this element and all child elements. /// </summary> public static IEnumerable<DependencyObject> ElementsAndSelf(this IEnumerable<DependencyObject> items) { return items.DrillDown(i => i.ElementsAndSelf()); } /// <summary> /// Returns a collection of descendant elements which match the given type. /// </summary> public static IEnumerable<DependencyObject> Descendants<T>(this IEnumerable<DependencyObject> items) where T : DependencyObject { return items.DrillDown<T>(i => i.Descendants()); } /// <summary> /// Returns a collection containing this element and all descendant elements. /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this IEnumerable<DependencyObject> items) where T : DependencyObject { return items.DrillDown<T>(i => i.DescendantsAndSelf()); } /// <summary> /// Returns a collection of ancestor elements which match the given type. /// </summary> public static IEnumerable<DependencyObject> Ancestors<T>(this IEnumerable<DependencyObject> items) where T : DependencyObject { return items.DrillDown<T>(i => i.Ancestors()); } /// <summary> /// Returns a collection containing this element and all ancestor elements. /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this IEnumerable<DependencyObject> items) where T : DependencyObject { return items.DrillDown<T>(i => i.AncestorsAndSelf()); } /// <summary> /// Returns a collection of child elements which match the given type. /// </summary> public static IEnumerable<DependencyObject> Elements<T>(this IEnumerable<DependencyObject> items) where T : DependencyObject { return items.DrillDown<T>(i => i.Elements()); } /// <summary> /// Returns a collection containing this element and all child elements. /// which match the given type. /// </summary> public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this IEnumerable<DependencyObject> items) where T : DependencyObject { return items.DrillDown<T>(i => i.ElementsAndSelf()); } } }
原文地址:http://www.scottlogic.co.uk/blog/colin/2010/03/linq-to-visual-tree/ by Regards, Colin E.
希望对你有帮助,如果有问题可以留言讨论。