FluentTreeView Part.5

  1. 1. 递归高亮
  2. 2. 高亮优先级
  3. 3. 配色太丑

递归高亮

先来修正选择高亮的问题吧。我更愿意图片中的效果称为递归高亮。递归高亮可以看作是子节点都处于非递归高亮状态。这么想的话实现起来应该非常容易。但我希望FluentTreeView能够更灵活一些。最好通过属性来切换高亮模式。

说来就来,要往TreeViewItem上加新的属性,要由TreeViewItem导出子类。要使TreeView的直接ItemContainer使用我们自定义的子类,需要继承TreeView的子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FluentTreeViewItem : TreeViewItem
{
public bool RecursiveHighlightMode
{
get { return (bool) GetValue (RecursiveHighlightModeProperty); }
set { SetValue (RecursiveHighlightModeProperty, value); }
}
// Using a DependencyProperty as the backing store for RecursiveHighlightMode. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RecursiveHighlightModeProperty =
DependencyProperty.Register ("RecursiveHighlightMode", typeof (bool), typeof (FluentTreeViewItem), new PropertyMetadata (false));
protected override DependencyObject GetContainerForItemOverride ()
{
return new FluentTreeViewItem ();
}
protected override bool IsItemItsOwnContainerOverride (object item)
{
return item is FluentTreeViewItem;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class FluentTreeView : TreeView
{
protected override DependencyObject GetContainerForItemOverride ()
{
return new FluentTreeViewItem ();
}
protected override bool IsItemItsOwnContainerOverride (object item)
{
return item is FluentTreeViewItem;
}
}

重写TreeView的GetContainerForItemOverride是常规操作,大家在自定义ItemContainer的时候肯定都做过。需要注意的是TreeViewItem也是ItemsControl,它的GetContainerForItemOverride也需要被重写。

接下来思考如何实现递归模式和非递归模式的切换。直接让子节点也处于高亮状态不错的办法,但高亮状态依赖的触发器,需要由IsSelected属性触发。为了界面而去修改数据,违背WPF数据驱动界面的原则。那么换一种思路,不再用分治的思路,而是直接在当前节点上全部处理掉。让父节点的高亮区域覆盖所有的子节点即可。宽度已经占满,只剩下高度了。对背景色,可以直接修改最外层面板的背景色。起指示作用的矩形,则可以修改其Grid.RowSpan让它充满整个高度。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<ControlTemplate TargetType="cc:FluentTreeViewItem">
<Grid x:Name="globalHighlight" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Rectangle Panel.ZIndex="20" Grid.RowSpan="1" x:Name="selector" Width="2" Visibility="Collapsed" Fill="Red" HorizontalAlignment="Left"></Rectangle>
<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 Background="Transparent" x:Name="selectorGrid">
<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>
<ItemsPresenter Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Converter={StaticResource BoolToVisibilityConverter}}" Grid.Row="1"></ItemsPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter TargetName="expander" Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="selector" Property="Visibility" Value="Visible"></Setter>
</Trigger>
<Trigger Property="RecursiveHighlightMode" Value="True">
<Setter TargetName="selector" Property="Grid.RowSpan" Value="2"></Setter>
</Trigger>
<MultiTrigger >
<MultiTrigger.Conditions>
<Condition Property="RecursiveHighlightMode" Value="True"></Condition>
<Condition Property="IsSelected" Value="True"></Condition>
</MultiTrigger.Conditions>
<Setter TargetName="globalHighlight" Property="Background" Value="Green"></Setter>
</MultiTrigger>
<MultiTrigger >
<MultiTrigger.Conditions>
<Condition Property="RecursiveHighlightMode" Value="False"></Condition>
<Condition Property="IsSelected" Value="True"></Condition>
</MultiTrigger.Conditions>
<Setter TargetName="selectorGrid" Property="Background" Value="Green"></Setter>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>

我们根据RecursiveHighlightMode的值在globalHighlightselectorGrid里选出作为选择高亮的控件,修改selectorGrid.RowSpanGrid.RowSpan会使布局子系统的Measure失效。在依赖属性的元数据中声明属性变化影响Measure阶段,就实现了动态切换高亮模式。

1
2
public static readonly DependencyProperty RecursiveHighlightModeProperty =
DependencyProperty.Register ("RecursiveHighlightMode", typeof (bool), typeof (FluentTreeViewItem), new FrameworkPropertyMetadata (false, FrameworkPropertyMetadataOptions.AffectsMeasure));

高亮优先级

回顾一下上一篇中模板的可视化树

1
2
3
4
5
6
Border(x:Name="root")
Grid(x:Name="selectorGrid")
Rectangle(x:Name="selector")
Grid
Expander
ContentPreserenter

当选中且鼠标悬空时,rootselectorGrid的触发器都生效。因为rootselectorGrid的父级容器,渲染时就被置于靠下的一层,这就导致了选择高亮覆盖鼠标高亮的表象。要改其实很简单,把这他们两个容器的位置互换就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<Grid Background="Transparent" x:Name="selectorGrid">
<Border>
<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>
<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>
</Grid>

配色太丑

剩下的就是一些小细节问题了。按照Fluent Design的标准,所有的图标都要使用Segoe MDL2 Assets实现,并且要根据Windows当前的主题模式和主题色动态更改颜色。

Fluent.WPF可以取得主题模式和主题色对应的画刷,但在使用它之前需要先在FluentTreeViewItem上新增属性,方便修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Brush MouseOverBrush
{
get { return (Brush) GetValue (MouseOverBrushProperty); }
set { SetValue (MouseOverBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for MouseOverBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MouseOverBrushProperty =
DependencyProperty.Register ("MouseOverBrush", typeof (Brush), typeof (FluentTreeViewItem), new PropertyMetadata (new SolidColorBrush (Colors.Red)));
public Brush SelectedBrush
{
get { return (Brush) GetValue (SelectedBrushProperty); }
set { SetValue (SelectedBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedBrushProperty =
DependencyProperty.Register ("SelectedBrush", typeof (Brush), typeof (FluentTreeViewItem), new PropertyMetadata (new SolidColorBrush (Colors.Green)));

选择高亮使用SystemAltMediumHighColorBrush,鼠标高亮使用SystemBaseMediumLowColorBrush,选择指示用的矩形使用SystemAccentColor

再把Expander用Segoe MDL2 Assets字体重新实现,我们的FluentTreeView就完成了。

递归高亮
fluent-treeview-recursive

非递归高亮
fluent-treeview-non-recursive


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