目前项目中效果(没上传,需要的留言)
以下例子 最终效果:
TreeView 折叠箭头,带垂直方向、水平方向层级线条(如图)
来看看怎么一步步实现吧~
补充一点,代码本地试过,现剪切的所以可能部分运行显示与截图不完全一样,
但基本结构是没有问题的,可以自己修改内容试试看!~
先做一个基础点的样式(图左侧)
(右侧是打印出来节点生成顺序,具体输出含义看本页的【转换器】)
<一>树结构的建立
写树节点的方式有两种:
【一种是前端xaml直接列出来内容】
【一种是后台绑定】,又分 ①绑定自定义节点类型的数据 或者 ②绑定默认类型数据。
(以下都来试试)
-----------------------------------------------------------------------------------------------------------------------
【一种是前端xaml直接列出来内容】
<TreeView>
<TreeViewItem/>
....
<TreeView x:Name="treeView_Set" Grid.RowSpan="2" FontSize="18" Margin="10,10,10,10" SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeViewItem TabIndex="1" Header="{DynamicResource ID_ST_2}">
<TreeViewItem TabIndex="2" Header="{DynamicResource ID_ST_1}"/>
<TreeViewItem Header="{DynamicResource ID_ST_2}">
<TreeViewItem TabIndex="10" Header="{DynamicResource ID_ST_3}"/>
<TreeViewItem TabIndex="20" Header="{DynamicResource ID_ST_3}"/>
<TreeViewItem TabIndex="30" Header="{DynamicResource ID_ST_3}"/>
<TreeViewItem TabIndex="40" Header="{DynamicResource ID_ST_3}">
<TreeViewItem TabIndex="50" Header="{DynamicResource ID_ST_3}"/>
<TreeViewItem TabIndex="60" Header="{DynamicResource ID_ST_3}"/>
</TreeViewItem>
</TreeViewItem>
<TreeViewItem TabIndex="70" Header="{DynamicResource ID_ST_3}"/>
<TreeViewItem TabIndex="80" Header="{DynamicResource ID_ST_3}"/>
<TreeViewItem TabIndex="90" Header="{DynamicResource ID_ST_3}"/>
</TreeViewItem>
</TreeView>
-----------------------------------------------------------------------------------------------------------------------
【一种是后台绑定】
①绑定自定义节点类型的数据
xaml前台把绑定元设定上,包括绑定格式等等 : =====>>>> 目前这样写,不会使用自定义的itemStyle
<TreeView x:Name="treeView_Set" Grid.RowSpan="2" FontSize="18" Margin="10,10,10,10" SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeViewItemIPU}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding TreeViewItemName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
后端自定义数据类型,并实现树结构:
TreeViewItemIPU 绑定对象的定义,与前端绑定的对象类型一致 (DataType),绑定元Children
再定义对象的属性,与前端绑定使用到的属性一致(TreeViewItemName)
InitDataSource() 实现数据初始化
最后一句 treeView_Set.Items.Add(root); 是将后台生成的树绑定到前端显示
/// <summary>
/// 可视化树节点的结构定义
/// </summary>
public class TreeViewItemIPU
{
private string _treeViewItemName;
public string TreeViewItemName
{
get { return _treeViewItemName; }
set { _treeViewItemName = value; }
}
private List<TreeViewItemIPU> _children = new List<TreeViewItemIPU>();
public List<TreeViewItemIPU> Children
{
get
{
return _children;
}
set
{
if (value != _children)
{
_children = value;
}
}
}
}
/// <summary>
/// 树形结构内容加载(初始化时调用即可)
/// </summary>
private void InitDataSource()
{
TreeViewItemIPU root = new TreeViewItemIPU() { TreeViewItemName = "Root" };
TreeViewItemIPU childLevel1 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1" };
TreeViewItemIPU childLevel1_1 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_1" };
TreeViewItemIPU childLevel1_1_1 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_1_1" };
TreeViewItemIPU childLevel1_1_1_1 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_1_1_1" };
TreeViewItemIPU childLevel1_1_1_2 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_1_1_2" };
TreeViewItemIPU childLevel1_1_1_3 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_1_1_3" };
TreeViewItemIPU childLevel1_1_1_4 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_1_1_4" };
childLevel1_1_1.Children.Add(childLevel1_1_1_1);
childLevel1_1_1.Children.Add(childLevel1_1_1_2);
childLevel1_1_1.Children.Add(childLevel1_1_1_3);
childLevel1_1_1.Children.Add(childLevel1_1_1_4);
childLevel1_1.Children.Add(childLevel1_1_1);
TreeViewItemIPU childLevel1_2 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_2" };
TreeViewItemIPU childLevel1_2_1 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_2_1" };
TreeViewItemIPU childLevel1_2_2 = new TreeViewItemIPU() { TreeViewItemName = "childLevel1_2_2" };
childLevel1_2.Children.Add(childLevel1_2_1);
childLevel1_2.Children.Add(childLevel1_2_2);
childLevel1.Children.Add(childLevel1_1);
childLevel1.Children.Add(childLevel1_2);
TreeViewItemIPU childLevel2 = new TreeViewItemIPU() { TreeViewItemName = "childLevel2" };
TreeViewItemIPU childLevel2_1 = new TreeViewItemIPU() { TreeViewItemName = "childLevel2_1" };
TreeViewItemIPU childLevel2_2 = new TreeViewItemIPU() { TreeViewItemName = "childLevel2_2" };
TreeViewItemIPU childLevel2_3 = new TreeViewItemIPU() { TreeViewItemName = "childLevel2_3" };
TreeViewItemIPU childLevel2_4 = new TreeViewItemIPU() { TreeViewItemName = "childLevel2_4" };
childLevel2.Children.Add(childLevel2_1);
childLevel2.Children.Add(childLevel2_2);
childLevel2.Children.Add(childLevel2_3);
childLevel2.Children.Add(childLevel2_4);
root.Children.Add(childLevel1);
root.Children.Add(childLevel2);
treeView_Set.Items.Add(root);
}
-----------------------------------------------------------------------------------------------------------------------
【一种是后台绑定】
②绑定默认类型数据
InitDataSource() 实现树结构初始化
最后一句 treeView_Set.Items.Add(root); 是将后台生成的树绑定到前端显示
private void InitDataSource()
{
TreeViewItem root = new TreeViewItem() { TabIndex = 1, Header = "Root" };
TreeViewItem childLevel1 = new TreeViewItem() { TabIndex = 2, Header = "childLevel1" };
TreeViewItem childLevel1_1 = new TreeViewItem() { TabIndex = 3, Header = "childLevel1_1" };
TreeViewItem childLevel1_1_1 = new TreeViewItem() { TabIndex = 4, Header = "childLevel1_1_1" };
TreeViewItem childLevel1_1_1_1 = new TreeViewItem() { TabIndex = 5, Header = "childLevel1_1_1_1" };
TreeViewItem childLevel1_1_1_2 = new TreeViewItem() { TabIndex = 6, Header = "childLevel1_1_1_2" };
TreeViewItem childLevel1_1_1_3 = new TreeViewItem() { TabIndex = 7, Header = "childLevel1_1_1_3" };
TreeViewItem childLevel1_1_1_4 = new TreeViewItem() { TabIndex = 8, Header = "childLevel1_1_1_4" };
childLevel1_1_1.Items.Add(childLevel1_1_1_1);
childLevel1_1_1.Items.Add(childLevel1_1_1_2);
childLevel1_1_1.Items.Add(childLevel1_1_1_3);
childLevel1_1_1.Items.Add(childLevel1_1_1_4);
childLevel1_1.Items.Add(childLevel1_1_1);
TreeViewItem childLevel1_2 = new TreeViewItem() { TabIndex = 9, Header = "childLevel1_2" };
TreeViewItem childLevel1_2_1 = new TreeViewItem() { TabIndex = 10, Header = "childLevel1_2_1" };
TreeViewItem childLevel1_2_2 = new TreeViewItem() { TabIndex = 11, Header = "childLevel1_2_2" };
childLevel1_2.Items.Add(childLevel1_2_1);
childLevel1_2.Items.Add(childLevel1_2_2);
childLevel1.Items.Add(childLevel1_1);
childLevel1.Items.Add(childLevel1_2);
TreeViewItem childLevel2 = new TreeViewItem() { TabIndex = 12, Header = "childLevel2" };
TreeViewItem childLevel2_1 = new TreeViewItem() { TabIndex = 13, Header = "childLevel2_1" };
TreeViewItem childLevel2_2 = new TreeViewItem() { TabIndex = 14, Header = "childLevel2_2" };
TreeViewItem childLevel2_3 = new TreeViewItem() { TabIndex = 15, Header = "childLevel2_3" };
TreeViewItem childLevel2_4 = new TreeViewItem() { TabIndex = 16, Header = "childLevel2_4" };
childLevel2.Items.Add(childLevel2_1);
childLevel2.Items.Add(childLevel2_2);
childLevel2.Items.Add(childLevel2_3);
childLevel2.Items.Add(childLevel2_4);
root.Items.Add(childLevel1);
root.Items.Add(childLevel2);
treeView_Set.Items.Add(root);
}
-----------------------------------------------------------------------------------------------------------------------
<二>树的样式模板
!!!有一点疑问:
我尝试将定义的 <TreeViewItem> 样式直接设定到 <TreeView> 的子节点属性中,但是不知道怎么设定;
考虑每个 item 都设定 style属性有点繁杂,所以就没给 <TreeViewItem> 定义 x:key,运用到范围内所有TreeViewItem控件
==> >>> 这点如果有谁知道怎么办,麻烦告诉我下~
【App.xaml】 树TreeView样式,含自定义:
①树折叠展开的ToggleButton样式(ExpandCollapseToggleStyle)
②树孩子节点的样式TreeViewItem
展开会显示一层节点垂直方向线,和每个节点水平方向线
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:vc="clr-namespace:ui.DataConverter">
<!-- 可视化树折叠展开的按钮样式 -->
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13" SnapsToDevicePixels="True">
<!-- Rectangle 9x9 pixels -->
<Rectangle Width="9" Height="9" Stroke="#919191" SnapsToDevicePixels="true">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,2" StartPoint="0.5,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Silver" Offset="0.5"/>
<GradientStop Color="LightGray" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- 画一个垂直方向的直线 -->
<Rectangle x:Name="ExpandPath" Width="1" Height="5" Stroke="Black" SnapsToDevicePixels="true"/>
<!-- 画一个水平方向的直线 -->
<Rectangle Width="5" Height="1" Stroke="Black" SnapsToDevicePixels="true"/>
</Grid>
<ControlTemplate.Triggers>
<!-- 通过IsChecked判断折叠还是展开 -->
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="ExpandPath" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<vc:TreeViewLineConverter x:Key="LineConverter"/>
<!-- (通用)可视化树孩子样式 -->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter>
<!--<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>-->
<!--<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>-->
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Foreground" Value="#FF565656"/>
<Setter Property="FontFamily" Value="FZLTZHUNHK"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="BorderThickness" Value="0"/>
<!--<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition MinWidth="20" Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="20"/>
<RowDefinition />
</Grid.RowDefinitions>
<!-- Connecting Lines -->
<!-- Horizontal line -->
<Rectangle x:Name="HorLn" Margin="9,0,0,0" Height="1" Stroke="#FF565656" SnapsToDevicePixels="True" StrokeDashCap="Square" StrokeDashArray="3,5" StrokeDashOffset="1"/>
<!-- Vertical line -->
<Rectangle x:Name="VerLn" Width="1" Stroke="#FF565656" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White" StrokeDashCap="Square" StrokeDashArray="3,5"/>
<ToggleButton x:Name="Expander"
Grid.Column="0"
Grid.Row="0"
ClickMode="Press"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="4"
HorizontalAlignment="Left"
Grid.Column="1"
Margin="1"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.ColumnSpan="2"
Grid.Column="1"
Grid.Row="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="#FF025BC7"/>
<Setter Property="Foreground" Value="#FFFFFFFF"/>
</Trigger>
<!--被选中后失去焦点-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#FF025BC7"/>
<Setter Property="Foreground" Value="#FFFFFFFF"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<!-- 当前层最后一个元素不画下方垂直线 -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
<Setter TargetName="VerLn" Property="Height" Value="15"/>
<Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
</DataTrigger>
<!-- Root第一个元素不显示上方垂直线 -->
<Trigger Property="TabIndex" Value="1">
<Setter TargetName="VerLn" Property="Margin" Value="0,12,1,0"/>
<Setter TargetName="VerLn" Property="Height" Value="Auto"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
【转换器】
对于当前层最后一个节点,不再画它水平方向线以下的垂直线,这里使用到转换器完成
依赖这个转换器,我试了下查看每个节点生成的顺序
==>>> System.Console.WriteLine(..) 打印出来生成顺序见【基本图】的右侧
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Data;
namespace ui.DataConverter
{
/// <summary>
/// 判断是否是构造当前层的最后一个元素
/// </summary>
class TreeViewLineConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
TreeViewItem item = (TreeViewItem)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
//System.Console.WriteLine(ic.Items.Count + " " + item.Header.ToString());
return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return false;
}
}
}
--------------------------------------------------------------------------------------------------------------------------
【再来,更新下按钮样式 ==> 最终效果图】
<!-- 可视化树折叠展开的按钮样式 -->
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#FF565656"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<!-- 画折叠样式 -->
<Border x:Name="CollapsePath" Height="14" Width="10" SnapsToDevicePixels="True" Background="{TemplateBinding Background}">
<!--<Rectangle x:Name="CollapsePath" Width="1" Height="5" Stroke="{TemplateBinding Background}" SnapsToDevicePixels="true"/>-->
<Path Data="M 0,0 8,7 0,14" Stretch="Fill"
Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1.5"
Height="14" Width="8"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="0,0,0,0">
<Path.LayoutTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Path.LayoutTransform>
</Path>
</Border>
<!-- 画展开折叠样式 -->
<Border x:Name="ExpandPath" Height="10" Width="14" SnapsToDevicePixels="True" Background="{TemplateBinding Background}" Visibility="Collapsed">
<!--<Rectangle x:Name="ExpandPath" Width="1" Height="5" Stroke="{TemplateBinding Background}" SnapsToDevicePixels="true"/>-->
<Path Data="M 0,0 8,7 0,14" Stretch="Fill"
Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1.5"
Height="14" Width="8"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="0,0,0,0">
<Path.LayoutTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="90"/>
<TranslateTransform/>
</TransformGroup>
</Path.LayoutTransform>
</Path>
</Border>
</Grid>
<ControlTemplate.Triggers>
<!-- 通过IsChecked判断折叠还是展开 -->
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="CollapsePath" Value="Collapsed"/>
<Setter Property="Visibility" TargetName="ExpandPath" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
【其他设定,我暂时没有用上】
<!-- 每个item文本的外观 -->
<!--<Style TargetType="HeaderedContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<StackPanel>
<Grid>
<Rectangle Stroke="{TemplateBinding Background}"/>
<ContentPresenter ContentSource="Header"/>
</Grid>
<Grid>
<Rectangle Fill="{TemplateBinding Background}"/>
<ContentPresenter ContentSource="Content"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>-->
<!-- 捕获键盘时的外观 -->
<!-- 键盘上下移动,左右控制展开折叠 -->
<!--<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>-->
!!!有一点疑问:
我尝试将定义的 <TreeViewItem> 样式直接设定到 <TreeView> 的子节点属性中,但是不知道怎么设定;
考虑每个 item 都设定 style属性有点繁杂,所以就没给 <TreeViewItem> 定义 x:key,运用到范围内所有TreeViewItem控件
==> >>> 这点如果有谁知道怎么办,麻烦告诉我下~