与之前发布的有修改!!
之前的版本,在某些情况下会失效(在鼠标悬停在某一行的空白处时,不会改变整行的背景色)。终其原因,是因为Border的背景为透明(Transparent),或者为null时,无法检测鼠标(我猜测是这样的)。因此,要解决这个问题,就必须让Border的内容,填充整个区域。
目标:做一个类似windows资源管理器的TreeView控件,用于展示层次化的数据结构。
功能要求:
鼠标悬停某一项时,改变整行的背景(而不是只改变内容部分的背景)
指定TreeView控件的数据源时,它默认会以TreeViewItem来展示每一个项。因此,TreeViewItem的样式,就非常重要。
一、TreeViewItem的布局
1.1 反编译TreeViewItem控件的Template属性,得到WPF默认的模板代码:
<!-- 为了方便各位学习,此处代码已经精简了布局无关的样式、触发器代码 -->
<ControlTemplate TargetType="TreeViewItem" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="19" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ToggleButton IsChecked="False" ClickMode="Press" Name="Expander"/>
<Border Name="Bd" Grid.Column="1">
<ContentPresenter ContentSource="Header" Name="PART_Header"
Content="{TemplateBinding HeaderedContentControl.Header}"/>
</Border>
<ItemsPresenter Name="ItemsHost" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2" />
</Grid>
</ControlTemplate>
此处布局的样式如下图所示:
从上图可知,每一个TreeViewItem的面板,都是一个两行、三列的Grid;
1:每个TreeViewItem都有一个ToggleButton,通过它的ControlTemplate,只是它的形状是一个三角形(每一项前面的三角形符号)
2:ContentPresenter控件表示内容控件,因为TreeViewItem是HeaderContentItemControl,所以设置ContentSource="Header",表示这个内容控件显示Header属性的内容。使用Border包裹它,用来设置背景色、边框等样式
3:ItemsPresenter表示该项的子项,它放在表格的第二行、第二列。
4:如此嵌套下去,则子项永远放在父项布局容器的第二行、第二列,因此产生类似缩进的效果。
5:代码中还有一个触发器(Trigger),用于当行为、属性值变成指定的值时,按指定的方式修改样式。
二、修改TreeViewItem模板
既然了解了TreeViewItem的默认模板构造,就可以动手自己修改模板了。要想整行选中,则必须有一个元素能填充整行,并且可以有IsMouseOver的触发器。很自然就想到了Border
如下代码所示:
<!-- 为了方便学习,精简了布局无关的代码 -->
<ControlTemplate TargetType="TreeViewItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- 不能绑定Background属性,否则IsMouseOver触发器会失效 -->
<Border Name="Bd" SnapsToDevicePixels="True"
BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
BorderBrush="{TemplateBinding Border.BorderBrush}" >
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 绑定IsChecked属性到父级元素的IsExpanded属性:否则点击ToggleButton不起作用 -->
<ToggleButton Grid.Column="0" ClickMode="Press" Name="Expander"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"/ >
<!-- HorizontalAlignment必须为Stretch,才能在鼠标进入该行的空白区域时,Border的触发器也发挥作用:默认的水平对齐为左对齐,导致内容区域只有一点点,使得鼠标只有在进入内容区域时,Border的触发器才起作用-->
<ContentPresente