【原创】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 ...
随机推荐
- .NET 开源工作流: Slickflow流程引擎高级开发(八) -- 审批网关(ApprovalOrSplit)模式的应用
前言:业务流程流转过程中,审批类型的节点是比较常见的,在审批操作中,常见的操作就是就是主管人员对待办事项进行同意或者拒绝.所以网关处理节点,就是需要对这两种审批结果进行预备处理,审批网关是在或分支(O ...
- git设置个人信息
git config --global user.name "username" 设置下自己提交的用户名 git config --global user.email " ...
- Jmeter监控插件
Jmeter-Plugins支持CPU.Memory.Swap.Disk和Network的监控,在测试过程中更加方便进行结果收集和统计分析. 一.准备工作: 1.下载Jmeter-Plugins插件, ...
- 思维导图MindManager的过滤主题功能如何使用
MindManager是一款多功能思维导图工具软件.但有的思维导图繁杂,用户只需要查看自己感兴趣的主题该怎么办呢?接下来,我就为大家详细介绍MindManager思维导图2020版的过滤主题功能,可以 ...
- 「LOJ 6287」诗歌
题面 LOJ 6287 Solution 枚举中间点\(j\),题目即求是否存在\(m\)使\(a[j]-m\)与\(a[j]+m\)分别在\(j\)两侧. 对于\(j\)左侧任意一个点\(i\),都 ...
- 【CF375D】Trees and Queries——树上启发式合并
(题面不是来自Luogu) 题目描述 有一个大小为n且以1为根的树,树上每个点都有对应的颜色ci.现给出m次询问v, k,问以v为根的子树中有多少种颜色至少出现了k次. 输入格式 第一行两个数n,m表 ...
- Java基础教程——Math类
Math Java这种级别的编程语言怎么可能没有数学相关的操作呢? java.lang.Math类提供了基本数学运算的方法. 该类是final的,说明不能被继承. 该类的构造方法是私有的(privat ...
- SpringCloud 源码系列(3)—— 注册中心 Eureka(下)
十一.Eureka Server 集群 在实际的生产环境中,可能有几十个或者几百个的微服务实例,Eureka Server 承担了非常高的负载,而且为了保证注册中心高可用,一般都要部署成集群的,下面就 ...
- PyQt(Python+Qt)学习随笔:树型部件QTreeWidget中的项编辑方法editTriggers、editItem和openPersistentEditor作用及对比分析
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在树型部件QTreeWidget中,有三种方法触发进行项数据的编辑:editTriggers触发编辑 ...
- PyQt学习随笔:Model/View中设置视图数据项可编辑的方法
在视图对象中调用setEditTriggers方法可以设置视图对象中的数据项是否可编辑以及编辑的触发方法. setEditTriggers方法是QAbstractItemView的方法,语法如下: s ...