您的位置:首页 > 其它

如何实现具有层次结构的 TreeView <四> (WPF/TreeView/Style/Template)

2011-08-05 08:58 671 查看
打造用户界面 (UI)

在上一节中,我们已经完成了数据的绑定工作,并通过 DataTemplate 实现了 Name 的纵向显示。本节将主要介绍下面几个内容:

1、对 TreeView 进行布局,实现层次结构的视图

2、设置 TreeViewItem 的样式,令其在不同操作下呈现出相应的状态

值得一提的是,在 dotNET 4.0 中提供的标准控件引入了 VisualStateGroup 类,有兴趣的朋友可以在自定义控件中试试。由于此处部分状态存在互斥的情况,所以仍使用了3.0的动画定义方式。

1、对 TreeView 进行布局,实现层次结构的视图

默认情况下 TreeView 中的节点是自上而下排列,从左向右展开。在 WPF 中要改变这样的布局并不困难,只需要改变承载节点的容器即可。

<TreeView.Style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush"
Value="#FFB0C4DE"/>
<Setter Property="BorderThickness"
Value="1"/>
<Setter Property="Foreground"
Value="#FF042271"/>
<Setter Property="Padding"
Value="15,20"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll"
Value="true"/>
<Setter Property="VerticalContentAlignment"
Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeView}">
<Grid>
<Rectangle x:Name="Rt"
Opacity="0"
Fill="#40000000">
<Rectangle.Effect>
<BlurEffect/>
</Rectangle.Effect>
</Rectangle>
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true">
<ScrollViewer x:Name="Scroll"
Padding="-20,0,0,0">
<!-- 默认样式中的标记是 <ItemsPresenter/> 使用的应该是 StackPanel -->
<!-- 此处我将其改为 WrapPanel 并设置 IsItemsHost="true" -->
<WrapPanel x:Name="ItemsHost"
IsItemsHost="True"
Margin="{TemplateBinding Padding}"
Orientation="Horizontal"/>
</ScrollViewer>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsGrouping"
Value="true">
<Setter Property="ScrollViewer.CanContentScroll"
Value="false"/>
</Trigger>
<Trigger Property="IsMouseOver"
Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Rt"
Storyboard.TargetProperty="Opacity"
Duration="0:0:0.5"
To="1"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Rt"
Storyboard.TargetProperty="Opacity"
Duration="0:0:0.5"
To="0"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter TargetName="Bd"
Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter TargetName="Bd"
Property="BorderBrush"
Value="{DynamicResource {x:Static SystemColors.InactiveBorderBrushKey}}"/>
</Trigger>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetName="Scroll"
Storyboard.TargetProperty="Padding"
DecelerationRatio="0.9"
Duration="0:0:3"
To="0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Style>
应用了该样式后将看到如下图所示的界面:



此时,我们已经实现了节点的横向排列以及从上往下展开。下面将介绍如何利用模板和触发器定制节点的样式。

2、设置 TreeViewItem 的样式,令其在不同操作下呈现出相应的状态

下面给出的 XAML 完整的代码,重新定义了 TreeViewItem 的边框、表示含有子节点的箭头、连接子节点的线段以及承载子节点的容器等。

至于操作状态,主要包括: 节点被选中、TreeView 失去焦点以及节点无效(IsEnabled=false)等。

<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush"
Value="#FF336699"/>
<Setter Property="BorderThickness"
Value="1"/>
<Setter Property="HorizontalAlignment"
Value="Center"/>
<Setter Property="HorizontalContentAlignment"
Value="Center"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Margin"
Value="5,0"/>
<Setter Property="Padding"
Value="10"/>
<Setter Property="HorizontalContentAlignment"
Value="Center"/>
<Setter Property="VerticalContentAlignment"
Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
HorizontalAlignment="Center">
<!-- 阴影效果 -->
<Rectangle x:Name="el_Shadow"
Fill="#30000000">
<Rectangle.Effect>
<BlurEffect />
</Rectangle.Effect>
</Rectangle>
<!-- 底色 -->
<Rectangle Fill="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<!-- 边框 -->
<Border x:Name="el_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="Center"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<ContentPresenter x:Name="PART_Header"
Grid.Row="0"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</Grid>
<!-- 用于表示节点展开/折叠的箭头 -->
<Grid x:Name="el_Arrow" Grid.Row="1"
Height="15">
<Path Data="M 1,1.5 L 4.5,5 L 8,1.5"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SnapsToDevicePixels="false"/>
</Grid>
<!-- 节点展开后与子节点间的连接线 -->
<Rectangle x:Name="el_Line"
Grid.Row="1"
Fill="{TemplateBinding BorderBrush}"
Width="1" Height="0"
Opacity="0"
SnapsToDevicePixels="True"/>
<!-- 表示子节点范围的线段 -->
<Border x:Name="el_Range"
Grid.Row="2"
Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1,1,1,0"
Height="0"
Opacity="0"
SnapsToDevicePixels="True"/>
<!-- 承载子节点的容器 -->
<WrapPanel x:Name="el_Host"
Grid.Row="3"
IsItemsHost="True"
Orientation="Horizontal"
Visibility="Collapsed"/>
</Grid>
<!-- 动画触发器 -->
<ControlTemplate.Triggers>
<MultiTrigger>
<!-- 当 ListViewItem 的 HasItems/IsExpanded 都为 true 时触发动画 -->
<MultiTrigger.Conditions>
<Condition Property="HasItems"
Value="true"/>
<Condition Property="IsExpanded"
Value="true"/>
</MultiTrigger.Conditions>
<!-- 1、显示承载子节点的容器 el_Host(WrapPanel) -->
<Setter TargetName="el_Host"
Property="Visibility"
Value="Visible"/>
<!-- 2、分别对 el_Line/el_Range/elArrow 对象的 Opacity/Height 属性应用动画效果 0.2s -->
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="el_Line"
Storyboard.TargetProperty="Height"
To="15"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Line"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Range"
Storyboard.TargetProperty="Height"
To="15"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Range"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Arrow"
Storyboard.TargetProperty="Height"
To="0"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Arrow"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="el_Line"
Storyboard.TargetProperty="Height"
To="0"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Line"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Range"
Storyboard.TargetProperty="Height"
To="0"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Range"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Arrow"
Storyboard.TargetProperty="Height"
To="15"
Duration="0:0:0.2"/>
<DoubleAnimation Storyboard.TargetName="el_Arrow"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
<!-- 展开/折叠节点触发器 显示/隐藏 承载子节点的容器 el_Host(WrapPanel) -->
<Trigger Property="IsExpanded"
Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="el_Host"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="el_Host"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<!-- 针对 WrapPanel 的 Opacity=0 时触发的动画 -->
<Trigger SourceName="el_Host"
Property="Opacity"
Value="0">
<Setter TargetName="el_Host"
Property="Visibility"
Value="Collapsed"/>
</Trigger>
<!-- 若不包含子节点 即 ListViewItem.HasItems=false 则隐藏 el_Host(WrapPaenl)/el_Arrow(指示箭头) -->
<Trigger Property="HasItems"
Value="false">
<Setter TargetName="el_Host"
Property="Visibility"
Value="Collapsed"/>
<Setter TargetName="el_Arrow"
Property="Visibility"
Value="Collapsed"/>
</Trigger>
<!-- 单个子节点触发器 此时将隐藏表示子节点范围的线段 el_Range 对象 -->
<DataTrigger Binding="{Binding ElementName=el_Host, Path=Children.Count}"
Value="1">
<Setter TargetName="el_Range"
Property="Visibility"
Value="Collapsed"/>
</DataTrigger>
<!-- 节点被选中触发器 -->
<Trigger Property="IsSelected"
Value="true">
<Setter TargetName="el_Border"
Property="Background"
Value="#FFC5D9F1"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="el_Shadow"
Storyboard.TargetProperty="Fill.Color"
To="#AB000000"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="el_Shadow"
Storyboard.TargetProperty="Fill.Color"
To="#30000000"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<!-- 节点被选中但 TreeView 失去焦点时的触发器 -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected"
Value="true"/>
<Condition Property="IsSelectionActive"
Value="false"/>
</MultiTrigger.Conditions>
<Setter TargetName="el_Border"
Property="Background"
Value="#FFDDD9C3"/>
</MultiTrigger>
<!-- 有效性 IsEnabled 触发器 -->
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>

至此,这篇文章已经完成。写这篇文章的原因,一是为了总结最近的学习成果,整理出一个完整的思路;二是分享成果,希望能与感兴趣的朋友共同提高,文中不足望高手指点!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐