【原创】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 ...
随机推荐
- Network_01
(从实践中学习TCP/IP协议读书笔记) 准备工作: 安装Kali Linux系统: 在VMWare中安装,选Debian 8.x 64bit,ISO镜像地址,在下载完镜像后,在VMWare中把镜像挂 ...
- python 定时任务框架apscheduler
文章目录 安装 基本概念介绍 调度器的工作流程 实例1 -间隔性任务 实例2 - cron 任务 配置调度器 方法一 方法二 方法三: 启动调度器 方法一:使用默认的作业存储器: 方法二:使用数据库作 ...
- css3系列之transform 详解skew
skew skewx skewy skewX() 倾斜该元素,里面填的是角度,下面↓ 你会看到,随着元素被倾斜,高度居然不变.聪明的你,一定会知道,高度不变,代表了,Y轴被拉伸了. 跟scale 同 ...
- Unity减少构建安装包的体积(210MB减小到7MB)
概述 项目简介 由于是公司内做的项目,不方便开源,就只分享优化过程吧. 项目信息 逐日是一个移动端单机小游戏,使用Unity开发,目前已将项目使用的Unity升级到2019.4.14f1c1 (3e5 ...
- C语言讲义——头文件
头文件.h Dev C++可以建C项目,也可以建C++项目,下面分C和C++两种情况讨论. c.h C语言中,头文件往往不是必须的,只是描述性的文件. 因此,C项目中可以没有.h文件. cpp.h 下 ...
- web服务器专题:tomcat基础及模块
Web服务器专题:Tomcat(一)基础架构 针对java系的经典服务器,打算系统的整理一下Tomcat的机制和一些原理,以此记录. 插一则题外话,关于tomat这个名字的由来:Tomcat 名称的由 ...
- JZOJ8月10日提高组反思
JZOJ8月10日提高组反思 T1 没想到怎么打 就去打暴力了 本来想拿个30不错了 结果\(AC\)了 话说回来,数据也挺小的 T2 不AC便爆0 就一个数据点 给不给打暴力的人活了 正解是状压DP ...
- Java进阶专题(十八) 系统缓存架构设计 (下)
前言 上章节介绍了Redis相关知识,了解了Redis的高可用,高性能的原因.很多人认为提到缓存,就局限于Redis,其实缓存的应用不仅仅在于Redis的使用,比如还有Nginx缓存,缓存队列等等.这 ...
- linux搭建harbor与使用
条件:安装docker&docker-compose 如未安装,请看:linux离线安装docker + docker-compose harbor 1.下载 下载地址:https://git ...
- springmvc<一> 一些特殊的Bean
Special Bean Types HandlerMapping 基于前置或后置拦截器映射请求到处理器,具体实现方式由子类决定, 两种主要的实现 ...