【原创】WPF TreeView带连接线样式的优化(WinFrom风格)
一、前言
之前查找WPF相关资料的时候,发现国外网站有一个TreeView控件的样式,是WinFrom风格的,样式如下,文章链接:https://www.codeproject.com/tips/673071/wpf-treeview-with-winforms-style-fomat


上面的右边的图片是用WPF实现的,看起来不错,实现的代码也比较简单,关键样式代码如下:
1 <!-- TreeViewItem -->
2 <Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
3 <Setter Property="Background" Value="Transparent"/>
4 <Setter Property="Padding" Value="1,0,0,0"/>
5 <Setter Property="Template">
6 <Setter.Value>
7 <ControlTemplate TargetType="{x:Type TreeViewItem}">
8 <Grid>
9 <Grid.ColumnDefinitions>
10 <ColumnDefinition MinWidth="19" Width="Auto"/>
11 <ColumnDefinition Width="Auto"/>
12 <ColumnDefinition Width="*"/>
13 </Grid.ColumnDefinitions>
14 <Grid.RowDefinitions>
15 <RowDefinition Height="Auto"/>
16 <RowDefinition/>
17 </Grid.RowDefinitions>
18
19 <!-- Connecting Lines -->
20 <Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1" Stroke="#DCDCDC" SnapsToDevicePixels="True"/>
21 <Rectangle x:Name="VerLn" Width="1" Stroke="#DCDCDC" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White"/>
22 <ToggleButton Margin="-1,0,0,0" x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"/>
23 <Border Name="Bd" Grid.Column="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
24 <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" MinWidth="20"/>
25 </Border>
26 <ItemsPresenter x:Name="ItemsHost" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"/>
27 </Grid>
28 <ControlTemplate.Triggers>
29
30 <!-- This trigger changes the connecting lines if the item is the last in the list -->
31 <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
32 <Setter TargetName="VerLn" Property="Height" Value="9"/>
33 <Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
34 </DataTrigger>
35 <Trigger Property="IsExpanded" Value="false">
36 <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
37 </Trigger>
38 <Trigger Property="HasItems" Value="false">
39 <Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
40 </Trigger>
41 <MultiTrigger>
42 <MultiTrigger.Conditions>
43 <Condition Property="HasHeader" Value="false"/>
44 <Condition Property="Width" Value="Auto"/>
45 </MultiTrigger.Conditions>
46 <Setter TargetName="PART_Header" Property="MinWidth" Value="75"/>
47 </MultiTrigger>
48 <MultiTrigger>
49 <MultiTrigger.Conditions>
50 <Condition Property="HasHeader" Value="false"/>
51 <Condition Property="Height" Value="Auto"/>
52 </MultiTrigger.Conditions>
53 <Setter TargetName="PART_Header" Property="MinHeight" Value="19"/>
54 </MultiTrigger>
55 <Trigger Property="IsSelected" Value="true">
56 <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
57 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
58 </Trigger>
59 <MultiTrigger>
60 <MultiTrigger.Conditions>
61 <Condition Property="IsSelected" Value="true"/>
62 <Condition Property="IsSelectionActive" Value="false"/>
63 </MultiTrigger.Conditions>
64 <Setter TargetName="Bd" Property="Background" Value="Green"/>
65 <Setter Property="Foreground" Value="White"/>
66 </MultiTrigger>
67 <Trigger Property="IsEnabled" Value="false">
68 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
69 </Trigger>
70 </ControlTemplate.Triggers>
71 </ControlTemplate>
72 </Setter.Value>
73 </Setter>
74 </Style>
LineConvert:
1 class TreeViewLineConverter : IValueConverter
2 {
3 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
4 {
5 TreeViewItem item = (TreeViewItem)value;
6 ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
7 return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
8 }
9
10 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
11 {
12 return false;
13 }
14 }
二、存在问题
作者提到2有个Bug:
1、添加新的项目到最后一项的时候,原本是最后一项的样式不会更新,结果就是下面这张图:
2、字体大小发生改变的时候,连接线也会出现异常;

上图中的TUYEN这一项的连接线没有更新
三、原因分析
由于作者在TreeViewItem的Template中使用了DataTrigger,并且Binding自身,那么就只有在他创建的时候,会去执行LineConvert进行判断,如果结果为True,就会设置垂直连接线VerLn的样式:
1 <!-- This trigger changes the connecting lines if the item is the last in the list -->
2 <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
3 <Setter TargetName="VerLn" Property="Height" Value="9"/>
4 <Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
5 </DataTrigger>
但是在以后的程序运行过程中,DataTrigger是接收不到任务绑定的通知,自然就不会进行重绘,那垂直连接线还是老样子,不会重绘了
四、解决方案
明白问题的原因后,自然好解决,不过我也是苦思摸索好几天,用Bing查了国外很多网站,也没有个好的方案;而先前因为墙的原因,没看到原文的评论,提到用附加属性来解决,不过代码一大串,也不如我这个方案简洁好用。
1 <Rectangle x:Name="VerLn" Width="1" Stroke="#DCDCDC" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White">
2 <Rectangle.Height>
3 <MultiBinding Converter="{StaticResource LineConverter}">
4 <MultiBinding.Bindings>
5 <Binding RelativeSource="{RelativeSource AncestorType=TreeView}" Path="ActualHeight" ></Binding>
6 <Binding RelativeSource="{RelativeSource AncestorType=TreeView}" Path="ActualWidth"></Binding>
7 <Binding RelativeSource="{RelativeSource TemplatedParent}"></Binding>
8 <Binding RelativeSource="{RelativeSource Self}"></Binding>
9 <Binding ElementName="Expander" Path="IsChecked"></Binding>
10 </MultiBinding.Bindings>
11 </MultiBinding>
12 </Rectangle.Height>
13 </Rectangle>
后台代码,LineConvert:
1 class TreeViewLineConverter : IMultiValueConverter
2 {
3 public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
4 {
5 double height = (double) values[0];
6
7 TreeViewItem item = values[2] as TreeViewItem;
8 ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
9 bool isLastOne = ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
10
11 Rectangle rectangle = values[3] as Rectangle;
12 if (isLastOne)
13 {
14 rectangle.VerticalAlignment = VerticalAlignment.Top;
15 return 9.0;
16 }
17 else
18 {
19 rectangle.VerticalAlignment = VerticalAlignment.Stretch;
20 return double.NaN;
21 }
22 }
23
24 public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
25 {
26 throw new NotImplementedException();
27 }
28 }
这里我对垂直线VerLn的Height属性使用了多重绑定,绑定的对象有TreeView的ActualWidth和ActualHeight,这两个是依赖属性的,只有数值发生变化,就会触发通知;垂直线的Height属性就能及时进行计算更新。
五、总结
相对于原文下面评论,提到使用附加属性,通过监听TreeView的属性ItemContainerGenerator的ItemsChanged事件,然后每一项TreeViewItem再判断自己是不是最后一项,我的这种解决方案真的是简单也容易理解。
在这几天的摸索过程,收获也蛮多,比如对依赖/附加属性,Adorner、路由事件,有幸拜读一些大佬的文章,才逐步加深上述功能的理解,而反观前端用Html/Css/Js就可以渲染各种各样的页面,不由得佩服,这里把TreeView的WinFrom风格样式共享出来,也希望能够帮助对WPF求知的朋友。
六、源码
1、原作者的代码:
2、优化后的代码:
【原创】WPF TreeView带连接线样式的优化(WinFrom风格)的更多相关文章
- WPF 重写微调自带的样式,ListView、DataGrid、TreeView等所有控件的默认样式
不知道各位在开发中有没有遇到这样的窘迫,开发一个UI,设计给出的效果图和自带的样式的区别很大,然后有的样式通过属性是修改不了的,比如TreeView的子项TreeViewItem,想完全透明背景色就做 ...
- WPF ScrollViewer(滚动条) 自定义样式表制作 再发一套样式 细节优化
艾尼路 出的效果图 本人嵌套 WPF ScrollViewer(滚动条) 自定义样式表制作 图文并茂 WPF ScrollViewer(滚动条) 自定义样式表制作 (改良+美化) 源代码
- WPF 自定义键盘焦点样式(FocusVisualStyle)
WPF 自带的键盘焦点样式是与传统控件样式搭配的,但 WPF 凭着其强大的自定义样式的能力,做出与传统控件样式完全不同风格的 UI 简直易如反掌.这时,其自带的键盘焦点样式(FocusVisualSt ...
- WPF 实现带蒙版的 MessageBox 消息提示框
WPF 实现带蒙版的 MessageBox 消息提示框 WPF 实现带蒙版的 MessageBox 消息提示框 作者:WPFDevelopersOrg 原文链接: https://github.com ...
- java生成带html样式的word文件
参考:http://blog.csdn.net/xiexl/article/details/6652230 最近在项目中需要将通过富文本编辑器处理过的文字转换为Word,查了很久,大家通常的解决办法是 ...
- WPF笔记(1.9 样式和控件模板)——Hello,WPF!
原文:WPF笔记(1.9 样式和控件模板)--Hello,WPF! 资源的另一个用途是样式设置: <Window > <Window.Resources> <St ...
- WPF 自带Datagrid编辑后无法更新数据源的问题
原文 WPF 自带Datagrid编辑后无法更新数据源的问题 解决办法: 在列的绑定属性里加上UpdateSourceTrigger,示例XAML如下 <DataGrid Grid.Row=& ...
- 百度地图API显示多个标注点带百度样式信息检索窗口的代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- WPF TreeView SelectedItemChanged called twice
How to avoid WPF TreeView SelectedItemChanged being called twice Very often, we need to execute some ...
随机推荐
- iOS Transform坐标变化
在使用CGContext时,由于Quartz 2D与UIKit坐标不一致,所以需要对context进行再一次的变化,达到预期的效果. 1. 不同坐标原点介绍 在Quartz 2D中,坐标原点在画布的左 ...
- kali 更新msf
用leafpad打开,方便复制粘贴 leafpad /etc/apt/sources.list 然后复制下面的源覆盖原本的 deb http://mirrors.ustc.edu.cn/kali ka ...
- sqlilab less1-less10
less-1 参数被单引号包裹,加单引号,闭合后绕过 less-2 参数没有被包裹,直接带入查询,不需要闭合 less-3 参数被 ('$id') 包裹,需要将他闭合 less-4 参数被小括号和双引 ...
- Word中如何调整MathType公式的间距
作为一名理工科的学生,经常会面对一大堆公式,那么就要掌握在Word中编辑公式的技能,那么怎样才能在Word中编辑美观的公式呢?为了方便大家的使用,下面就详细介绍在Word中调整MathType公式间距 ...
- jQuery 第一章 $()选择器
jquery 是什么? jquery 其实就是一堆的js函数(js库),也是普通的js而已. 有点像我们封装一个函数,把他放到单独的js 文件,等待有需要的时候调用它. 那么使用它有啥好处呢? jqu ...
- 记录一下Comparator的用法
Collections.sort(res, new Comparator<ArrayList<Integer>>() { @Override ...
- Luogu P4306 JSOI2010 连通数
tarjan有向图缩点的基础应用.把原图中某点的连通数转化为反向图中"能够到达某点的个数".缩点后,每个新点的贡献等于 原dcc大小 * f[i] 其中f[i]表示(包括该点自身) ...
- 痞子衡嵌入式:探析开启CRC完整性校验的IAR工程生成.out和.bin文件先后顺序
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是开启CRC完整性校验的IAR工程生成.out和.bin文件先后顺序问题. 痞子衡之前写了一篇 <在IAR开发环境下为工程开启CRC ...
- C语言讲义——指针函数和函数指针
指针函数 返回值是指针的函数,如void* malloc(...) #include<stdio.h> #include<stdlib.h> #include<strin ...
- 一次SQL注入导致的"越权"
原文来自SecIN社区-作者:tkswifty 相关背景 在实际的业务开发中,SQL交互往往是业务系统中不可或缺的一项.在Java中提供了类似Mybatis.Hibernate.SpringDat ...