WPF 的 TreeView 控件自带的样式如图 1 的左边所示,节点前的箭头还不错,但是选中效果实在是不给力,如果想更加华丽的话,那么很有必要把 TreeView 的样式好好自定义一番。我最后得到的结果如图 1 的右边所示,是一个 Win8 风格的 TreeView。
图 1 TreeView 样式对比
在 WPF 中,自定义控件的外观是一件非常简单的事情,但对于 TreeView 来说,最大的困难则在于如何做到节点的整行选择。这是因为 TreeView 的项模板默认是如图 2 所示的。
图 2 TreeView 默认的项模板
最外层是一个三列两行的 Grid,其中第一列用于放置 Expander(节点前的箭头),剩下的列则放置 PART_Header(标头内容)和子节点列表。因为子节点列表前空出了 Expander 那一列,所以 TreeView 的每层自然就有了缩进,同时也导致绘制边框时,无法令边框填满整行——因为不知道当前节点之前有多少层缩进。虽然也有取巧的办法,例如在绘制边框时,令 Margin.Left 足够小,但这仅适用于无边框背景,在定义 Win8 样式时不可用。
所以需要有一种办法来计算出当前节点所在的层次(即深度)才可以。这里提供了一个好办法,即通过在可视化树中沿着 TreeViewItem 的父对象向上遍历,从而得到 TreeViewItem 所在的深度,代码如下所示:
public static int GetDepth(this TreeViewItem item) { int depth = 0; while ((item = item.GetAncestor<TreeViewItem>()) != null) { depth++; } return depth; } public static T GetAncestor<T>(this DependencyObject source) where T : DependencyObject { do { source = VisualTreeHelper.GetParent(source); } while (source != null && !(source is T)); return source as T; }
这里需要使用 VisualTreeHelper.GetParent 方法来得到父对象,直接用 item.Parent 得到的会是 null。
得到了深度,接下来就将 TreeViewItem 的模板重新定义一下,如图 3 所示。
图 3 自定义的 TreeView 项模板
在自定义的模板中,所有节点都是使用 StackPanel 层叠排列的,所以此时是没有层次关系的,Border 是可以整行显示的。层次关系则是通过为 Grid 指定不同的 Margin.Left 来实现的,这就需要根据当前节点的深度,计算出需要缩进的距离。计算过程是利用 IValueConverter 来完成的:
<ControlTemplate.Resources> <!-- 计算节点缩进的转换器 --> <cw:IndentConverter Indent="10" MarginLeft="5" x:Key="IndentConverter" /> </ControlTemplate.Resources> <Grid Margin="{Binding Converter={StaticResource IndentConverter}, RelativeSource={RelativeSource TemplatedParent}}" />
public sealed class IndentConverter : IValueConverter { public double Indent { get; set; } public double MarginLeft { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { TreeViewItem item = value as TreeViewItem; if (item == null) { return new Thickness(0); } return new Thickness(this.MarginLeft + this.Indent * item.GetDepth(), 0, 0, 0); } }
这里的缩进转换器,我定义了两个属性:Indent 和 MarginLeft,这是因为我将缩进距离由 19 调整到了 12,这样会使树更好看点,但会导致根节点距离左边框过近,所以就加入了 MarginLeft,使得根节点离左边框更远些,如图 4 所示。
图 4 缩进距离对比
最后,就是样式的调整了。Win8 中的资源管理器树状列表的样式如图 5 所示,这个样式与 Win7 差不多,但是没有了渐变和圆角,实现起来要容易一些。需要注意的是被选中的节点,在鼠标经过的时候是会改变颜色的(虽然不是很明显),而且箭头无论是展开还是收起状态,在鼠标经过的时候都会变成蓝色。样式的定义没有什么好说的,一些颜色也在下图中标注出来了。
图 5 Win8 中的资源管理器
Win8 资源管理器树状列表最华丽的一点是,当控件没有焦点且鼠标未经过的时候,所有箭头是隐藏的,有焦点的时候才出现,可惜这个不知道该怎么实现,只好先放下了。
所有的样式定义我都放在了 \Resources\Win8Theme\TreeView.xaml 文件中,这是一个资源字典,只要在需要的地方使用 <ResourceDictionary Source="Resources/Win8Theme/TreeView.xaml" /> 导入资源字典,TreeView 控件就可以以 Win8 样式显示。完整的代码和示例可以在这里下载。