No.8948 实现Windows 7样式Aero TreeView控件(二):实现整行选中
记得原来做Winfrom通过Item的Bounds可以获得整行的区域,但是在WPF中进行了几个布局方式都没能成功!
VS中的解决方案效果布局如下:
这样在第一行第二列方式Border控件,此为选中区域。而如果要实现右侧系统的效果,必须要在选中区域的时候补齐第二行第一列的宽度,但是如果在TreeViewItem的Template不设置子节点列表缩进的话,将无法定位子节点列表缩进!
对比了Winform的TreeNode类型有两个关键的属性:FullPath和Level。只要知道一个就可以根据Indent算出相应的缩进宽度,从这样的思路上,这个Item布局结构就要更改如下了:
代码结构如下:
<StackPanel>
<Border x:Name=”itemBorder”>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinitions Width=”19” />
<ColumnDefinitions Width=”*” />
</Grid.ColumnDefinitions>
<Path x:Name=”TreeArrow” Grid.Column=”0” />
<ContentPresenter ContentSource=”Header” Grid.Column=”1” />
</Grid>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
可是此时的子节点列表没有缩进怎么办?
做几个预备动作,在TreeViewItem里面有一个TreeNode的特殊特性,叫做Level,获得节点所在的层级,在这里我做了个Extensions类:
public static class TreeViewItemExtensions
{
public static int GetDepth(this TreeViewItem item)
{
FrameworkElement elem = item;
while (elem.Parent != null)
{
var tvi = elem.Parent as TreeViewItem;
if (null != tvi)
return tvi.GetDepth() + 1;
elem = elem.Parent as FrameworkElement;
}
return 0;
}
}
用来获得TreeViewItem在TreeView里面的层级深度。然后就是怎么将缩进的绑定到它该在的地方了!
在上面那段TreeViewItem的Template代码里面的itemBorder就是节点项的整体范围了,如果我们想让选中边框始终保持Aero样式中满行选中的状态就只能在itemBorder中的Grid上做手脚了,上面扩展了TreeViewItem对象,可以获取的到层级深度,而缩进值则等于节点层级和缩进值的乘积,而应用缩进值需要实现一个缩进的转换类型方法:
public class IndentConverter:IValueConverter
{
public double Indent{ get; set; }public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var item = value as TreeViewItem;
if (null == item)
return new Thickness(0);
return new Thickness(Indent* item.GetDepth(), 0, 0, 0);
}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
然后就是改造上一段模板内容。完整代码如下:
<Style TargetType="{x:Type TreeViewItem}" x:Key="aaa">
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<ControlTemplate.Resources>
<o2ds:IndentConverter Indent="19" x:Key="indentConverter" />
</ControlTemplate.Resources>
<StackPanel>
<Border Name="itemBackground" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<Grid Margin="{Binding Converter={StaticResource lengthConverter},RelativeSource={RelativeSource TemplatedParent}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" x:Name="ArrowButton" Style="{StaticResource TreeViewArrowButtonStyle}"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press" />
<ContentPresenter Grid.Column="1" x:Name="PART_Header" ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Grid>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel><ControlTemplate.Triggers>
Trigger something…
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Grid Margin="{Binding Converter={StaticResource indentConverter},RelativeSource={RelativeSource TemplatedParent}}">
indentConverter对象承担了获取深度以及转换边距的任务。由此我们就完成了实现整行选中TreeViewItem的任务,至于Aero样式的效果细节,后面放出。