AvaloniaUIのTreeViewが遅い問題の解決法
AvaloniaUIのTreeViewは項目数が多くなると顕著に重くなる。それを解消しようともがいた経緯。
使用環境
Windows 11 : Home 22H2 + Visual Studio Community 2022(64bit) Version 17.12.3
Avalonia 11.2.3
TreeViewは項目数が増えると遅くなる
下記にあるようにTreeViewは項目数が多くなると顕著に動作が重くなる。
項目数が多い時にはTreeGridViewをつかうことが推奨されている。
When you need to display a large amount of data in a DataGrid or a TreeView with many nodes, it is recommended to use the TreeDataGrid control. TreeDataGrid is built from scratch and provides better performance than the normal DataGrid. It supports virtualization and is particularly useful if you need a virtualized tree, as it has hierarchical data templates.
TreeGridViewを使ってTreeControlを実装してみた
以下でおこなったように、winformsのTreeControlのようなものをTreeGridViewを使って実装してみた
TreeGridViewを使うにはStyleの設定が必要
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AjkAvaloniaLibs.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
<StyleInclude
Source="avares://Avalonia.Controls.TreeDataGrid/Themes/Fluent.axaml"/>
</Application.Styles>
</Application>
忘れると、エラーは出ないのに描画されない。
ViewModelはViewと必ず別にしないといけない。
Viewのコードビハインドに特に書くことがない場合に、ViewのコードビハインドにViewModelを書いて、Viewの中でDataContext = thisとするような
手抜きをすると、TreeGridViewは動作しなくなる。これをやると、エラーは出ないのに描画されない。
Styleの設定 : 行の高さを変える
Bindingがとても難解。
複雑なVisualTreeでいろんなTempleteが定義をされており、行の高さを変えるのがなかなか難しい。
Templete内で固定されている高さ要素があり、TreeGridViewのソースコードを見ながらいろいろ上書きする必要がありそう。
<TreeDataGrid.Styles>
<Style Selector="TreeDataGridRow">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Height" Value="{Binding RowHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
<Setter Property="Margin" Value="0"/>
</Style>
<Style Selector="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="MinHeight" Value="0"/>
</Style>
<Style Selector="Border#CellBorder > DockPanel > Border">
<Setter Property="Height" Value="{Binding FontSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
<Setter Property="Width" Value="{Binding FontSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</Style>
<Style Selector="Border#CellBorder > DockPanel > Border > ToggleButton">
<Setter Property="Height" Value="{Binding FontSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
<Setter Property="Width" Value="{Binding FontSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</Style>
<Style Selector="TreeDataGridExpanderCell">
<Setter Property="MinHeight" Value="0"/>
</Style>
<!-- fix TreeDataGridExpanderCell template to eliminate CellBorder.DockPanel.Border.With/Height -->
<Style Selector="TreeDataGridExpanderCell">
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="CellBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Indent, Converter={x:Static ctrl:IndentConverter.Instance}}"
>
<DockPanel>
<Border x:Name="ShiftBox"
DockPanel.Dock="Left"
Margin="4 0"
Width="{Binding FontSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" Height="12">
<ToggleButton Theme="{StaticResource TreeDataGridExpandCollapseChevron}"
Focusable="False"
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}"
IsVisible="{TemplateBinding ShowExpander}" />
</Border>
<Decorator Name="PART_Content" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- expand/collapse icon shape 変更 -->
<Style Selector="ToggleButton:checked /template/ Path#ChevronPath">
<Setter Property="Data" Value="{StaticResource TreeViewItemExpandedChevronPathData}" />
</Style>
<Style Selector="ToggleButton:unchecked /template/ Path#ChevronPath">
<Setter Property="Data" Value="{StaticResource TreeViewItemCollapsedChevronPathData}" />
</Style>
</TreeDataGrid.Styles>
</TreeDataGrid>
でもできなかった
なんとなく動いてみえたものの、Itemのプロパティを変更したときにViewが変更されない問題が解消できなかった。
ListBoxを使って自前で実装する
しょうがないのでListBoxを使って自分で実装してみた。
NodesのUpdateをControlに伝達して、Viewをそのたびに更新する方式。
結果的には単純なコントロールの組み合わせで自分で組んでしまったほうが手っ取り早い感じがした。
Discussion