一、前言

  之前查找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风格)的更多相关文章

  1. WPF 重写微调自带的样式,ListView、DataGrid、TreeView等所有控件的默认样式

    不知道各位在开发中有没有遇到这样的窘迫,开发一个UI,设计给出的效果图和自带的样式的区别很大,然后有的样式通过属性是修改不了的,比如TreeView的子项TreeViewItem,想完全透明背景色就做 ...

  2. WPF ScrollViewer(滚动条) 自定义样式表制作 再发一套样式 细节优化

    艾尼路 出的效果图 本人嵌套 WPF ScrollViewer(滚动条) 自定义样式表制作 图文并茂 WPF ScrollViewer(滚动条) 自定义样式表制作 (改良+美化) 源代码

  3. WPF 自定义键盘焦点样式(FocusVisualStyle)

    WPF 自带的键盘焦点样式是与传统控件样式搭配的,但 WPF 凭着其强大的自定义样式的能力,做出与传统控件样式完全不同风格的 UI 简直易如反掌.这时,其自带的键盘焦点样式(FocusVisualSt ...

  4. WPF 实现带蒙版的 MessageBox 消息提示框

    WPF 实现带蒙版的 MessageBox 消息提示框 WPF 实现带蒙版的 MessageBox 消息提示框 作者:WPFDevelopersOrg 原文链接: https://github.com ...

  5. java生成带html样式的word文件

    参考:http://blog.csdn.net/xiexl/article/details/6652230 最近在项目中需要将通过富文本编辑器处理过的文字转换为Word,查了很久,大家通常的解决办法是 ...

  6. WPF笔记(1.9 样式和控件模板)——Hello,WPF!

    原文:WPF笔记(1.9 样式和控件模板)--Hello,WPF! 资源的另一个用途是样式设置: <Window >  <Window.Resources>    <St ...

  7. WPF 自带Datagrid编辑后无法更新数据源的问题

    原文  WPF 自带Datagrid编辑后无法更新数据源的问题 解决办法: 在列的绑定属性里加上UpdateSourceTrigger,示例XAML如下 <DataGrid Grid.Row=& ...

  8. 百度地图API显示多个标注点带百度样式信息检索窗口的代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  9. WPF TreeView SelectedItemChanged called twice

    How to avoid WPF TreeView SelectedItemChanged being called twice Very often, we need to execute some ...

随机推荐

  1. .NET 开源工作流: Slickflow流程引擎高级开发(八) -- 审批网关(ApprovalOrSplit)模式的应用

    前言:业务流程流转过程中,审批类型的节点是比较常见的,在审批操作中,常见的操作就是就是主管人员对待办事项进行同意或者拒绝.所以网关处理节点,就是需要对这两种审批结果进行预备处理,审批网关是在或分支(O ...

  2. git设置个人信息

    git config --global user.name "username" 设置下自己提交的用户名 git config --global user.email " ...

  3. Jmeter监控插件

    Jmeter-Plugins支持CPU.Memory.Swap.Disk和Network的监控,在测试过程中更加方便进行结果收集和统计分析. 一.准备工作: 1.下载Jmeter-Plugins插件, ...

  4. 思维导图MindManager的过滤主题功能如何使用

    MindManager是一款多功能思维导图工具软件.但有的思维导图繁杂,用户只需要查看自己感兴趣的主题该怎么办呢?接下来,我就为大家详细介绍MindManager思维导图2020版的过滤主题功能,可以 ...

  5. 「LOJ 6287」诗歌

    题面 LOJ 6287 Solution 枚举中间点\(j\),题目即求是否存在\(m\)使\(a[j]-m\)与\(a[j]+m\)分别在\(j\)两侧. 对于\(j\)左侧任意一个点\(i\),都 ...

  6. 【CF375D】Trees and Queries——树上启发式合并

    (题面不是来自Luogu) 题目描述 有一个大小为n且以1为根的树,树上每个点都有对应的颜色ci.现给出m次询问v, k,问以v为根的子树中有多少种颜色至少出现了k次. 输入格式 第一行两个数n,m表 ...

  7. Java基础教程——Math类

    Math Java这种级别的编程语言怎么可能没有数学相关的操作呢? java.lang.Math类提供了基本数学运算的方法. 该类是final的,说明不能被继承. 该类的构造方法是私有的(privat ...

  8. SpringCloud 源码系列(3)—— 注册中心 Eureka(下)

    十一.Eureka Server 集群 在实际的生产环境中,可能有几十个或者几百个的微服务实例,Eureka Server 承担了非常高的负载,而且为了保证注册中心高可用,一般都要部署成集群的,下面就 ...

  9. PyQt(Python+Qt)学习随笔:树型部件QTreeWidget中的项编辑方法editTriggers、editItem和openPersistentEditor作用及对比分析

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在树型部件QTreeWidget中,有三种方法触发进行项数据的编辑:editTriggers触发编辑 ...

  10. PyQt学习随笔:Model/View中设置视图数据项可编辑的方法

    在视图对象中调用setEditTriggers方法可以设置视图对象中的数据项是否可编辑以及编辑的触发方法. setEditTriggers方法是QAbstractItemView的方法,语法如下: s ...