FluentTreeView Part.4

这篇起我们正式开始实现FluentTreeView。先看看图片 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时,高亮色就出现了我们不需要的左边距。这不符合我们的要求,所以不能用递归的方式来实现不同层级的缩进。 recursive-indent 我们所想要的是,每一层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();
    }
}

选择高亮已经差不多了,但鼠标高亮还有点问题 fluent-tree-view-with-selector

一番搜索发现,当前鼠标悬空的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>

fluent-tree-view-mouseover-fix

看起来已经像模像样了,但还有瑕疵:

  • 选择高亮并不对所有的子节点生效。
  • 鼠标高亮没有覆盖选择高亮
  • 配色不对,丑

我们会在下一篇中改正这些问题


源代码参见 https://github.com/Verrickt/Melchior-Sample/tree/master/FluentTreeView_Part4

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计