一、前言

  之前查找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、优化后的代码:

最新文章

  1. Asia Hong Kong Regional Contest 2016
  2. MySQL 5.5编译安装
  3. 关于移动端常用的盒模型与flex布局
  4. Bootstrap 3 简介
  5. mysql 源码包 有的版本 可能没有 CMakeCache.txt
  6. 测试App运行状态
  7. YII千万级PV架构经验分享--理论篇
  8. asp.net mvc 提交model 接收不了
  9. c 按输入的字母来输出对应效果
  10. MySQL远程登陆错误
  11. 2017值得一瞥的JavaScript相关技术趋势
  12. Zookeeper Api
  13. mongo分片集群部署
  14. 解决eclipse的自动换行问题。
  15. flink 读取kafka 数据,partition分配
  16. Visual Studio学习记录
  17. CentOS6.8下安装mysql
  18. 第 8 章 容器网络 - 053 - overlay 是如何隔离的?
  19. taro 组件的外部样式和全局样式
  20. MongoDB CPU使用较高,如何排查?

热门文章

  1. 网页中审查元素(按F12)与查看网页源代码的区别
  2. 异或加密 - cr2-many-time-secrets(攻防世界) - 异性相吸(buuctf)
  3. FL studio系列教程(十四):如何在FL Studio播放列表中排列样式
  4. 【python】Matplotlib作图常用marker类型、线型和颜色
  5. 语法解析器续:case..when..语法解析计算
  6. 使C语言实现面向对象的三个要素,你掌握了吗?
  7. Git基本操作(一)
  8. flink:StreamExecutionEnvironment、DataStream和Transformation与StreamOperator
  9. LeetCode 028 Implement strStr()
  10. 第十三章、Designer中的按钮Buttons组件详解