这篇起我们正式开始实现FluentTreeView。先看看图片
TreeView左侧有一选择高亮,Item有一鼠标高亮。这两个高亮与整个TreeView一样宽。
再回想一下我们上一篇用Expander实现的TreeView,Item的缩进是如何实现的?
- |
Column1 |
Column2 |
Row1 |
Expander |
ContentPreserenter |
Row2 |
/ |
ItemsPreserenter |
ItemsPreserenter将会被展开为
1 2 3 4
| ItemsPanel TreeViewItem TreeViewItem TreeViewItem
|
这就意味着,下一级的TreeViewItem永远处于上一级TreeViewItem的第二列里。将第一列的列宽设置为定值,就实现了各个层级的缩进。
进一步思考,如果我们在这样的结构里去修改TreeViewItem的面板的背景色当作高亮的话,那这个面板自身也是被缩进的。
我们试一试,把Style改成这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <Style TargetType="TreeViewItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TreeViewItem"> <Grid x:Name="root"> <Grid.ColumnDefinitions> <ColumnDefinition Width="24"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Expander IsExpanded="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Mode=TwoWay}" x:Name="expander"></Expander> <ContentPresenter VerticalAlignment="Center" Grid.Column="1" ContentSource="Header"></ContentPresenter> <ItemsPresenter Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Converter={StaticResource BoolToVisibilityConverter}}" Grid.Row="1" Grid.Column="1"></ItemsPresenter> </Grid> <ControlTemplate.Triggers> <Trigger Property="HasItems" Value="False"> <Setter TargetName="expander" Property="Visibility" Value="Collapsed"></Setter> </Trigger> <Trigger Property="IsSelected" Value="True"> <Setter TargetName="root" Property="Background" Value="Red"></Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
|
在选中非第一级Item时,高亮色就出现了我们不需要的左边距。这不符合我们的要求,所以不能用递归的方式来实现不同层级的缩进。
我们所想要的是,每一层TreeViewItem的宽度都与TreeView本身保持一致,所以TreeViewItem自己必须是打平的。需要特殊处理的是ContentPreserenter。可以根据TreeViewItem的层级计算出所需要的左边距。
再次考虑整个TreeViewItem的布局,鼠标和选择高亮应该撑满可用宽度,倒三角符号(▽)和ContentPreserenter则应该随着层级缩进。有了这些做参照,很容易能写出这样的模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <Grid > <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Grid x:Name="root"> <Rectangle x:Name="selector" Width="2" Visibility="Collapsed" Fill="Green" HorizontalAlignment="Left"></Rectangle> <Grid Margin="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem},Converter={StaticResource TreeLevelToIndentConverter}}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="24"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Expander IsExpanded="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Mode=TwoWay}" x:Name="expander"></Expander> <ContentPresenter VerticalAlignment="Center" Grid.Column="1" ContentSource="Header"></ContentPresenter> </Grid> </Grid> <ItemsPresenter Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Converter={StaticResource BoolToVisibilityConverter}}" Grid.Row="1"></ItemsPresenter> </Grid>
|
TreeLevelToIndentConverter
是根据等级计算缩进的Converter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class TreeLevelToIndentConverter : IValueConverter { public Thickness Margin { get; set; } public object Convert (object value, Type targetType, object parameter, CultureInfo culture) { if (value is TreeViewItem ti) { int level = 0; FrameworkElement current = ti; do { if (VisualTreeHelper.GetParent (current) is FrameworkElement fe) { if (fe is TreeViewItem) { level++; } if (fe is TreeView) { break; } current = fe; } } while (current != null); return new Thickness (Margin.Left * level, 0, Margin.Right * level, 0); } return value; } public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
|
选择高亮已经差不多了,但鼠标高亮还有点问题
一番搜索发现,当前鼠标悬空的TreeViewItem以及它所有的祖先的IsMouseOver
触发器都会起作用。
当然,你也可以像我一样偷懒:给Border设置MouseOver的触发器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <Border x:Name="root"> <Border.Style> <Style TargetType="Border"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Red"></Setter> </Trigger> </Style.Triggers> </Style> </Border.Style> <Grid> <Rectangle x:Name="selector" Width="2" Visibility="Collapsed" Fill="Green" HorizontalAlignment="Left"></Rectangle> <Grid Margin="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem},Converter={StaticResource TreeLevelToIndentConverter}}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="24"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Expander IsExpanded="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Mode=TwoWay}" x:Name="expander"></Expander> <ContentPresenter VerticalAlignment="Center" Grid.Column="1" ContentSource="Header"></ContentPresenter> </Grid> </Grid> </Border>
|
看起来已经像模像样了,但还有瑕疵:
- 选择高亮并不对所有的子节点生效。
- 鼠标高亮没有覆盖选择高亮
- 配色不对,丑
我们会在下一篇中改正这些问题
源代码参见
https://github.com/Verrickt/Melchior-Sample/tree/master/FluentTreeView_Part4