自定义树视图的虚拟化
virtualization for custom treeview
我制作了一个包含多列的自定义 TreeView。一切都很好,直到树中有很多项目。
我尝试通过 VirtualizingPanel.IsVirtualizing="True"
启用虚拟化(如果您是 < .NET 4.5,将是 VirtualizingStackPanel.IsVirtualizing
)但它不仅没有加速,它实际上使加载时间均匀更糟。
在普通的 TreeView 上,这个 属性 可以解决问题,但我找不到让它在我的自定义 Tree
上工作的方法
TreeViewItem.cs
public class TreeListViewItem : TreeViewItem
{
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
}
TreeListView.cs
public class TreeListView : TreeView
{
public GridViewColumnCollection Columns { get; set; }
public TreeListView()
{
Columns = new GridViewColumnCollection();
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
}
Node.cs
public class Node
{
public string Data { get; set; }
public List<Node> Children { get; set; }
public Node(string data)
{
Data = data;
Children = new List<Node>();
}
}
ViewModel.cs
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; private set; }
public ViewModel()
{
Nodes = new ObservableCollection<Node>();
Node parent = new Node("Parent");
for (int i = 0; i < 5000; i++)
parent.Children.Add(new Node(i.ToString()));
Nodes.Add(parent);
}
}
MainWindow.xaml
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFTest"
Title="Test"
mc:Ignorable="d"
Width="200">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TreeListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListViewItem">
<StackPanel>
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}" >
</GridViewRowPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:TreeListView ItemsSource="{Binding Nodes}" VirtualizingPanel.IsVirtualizing="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"/>
</TreeView.ItemTemplate>
<local:TreeListView.Columns>
<GridViewColumn Header="Test" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
</local:TreeListView>
</Grid>
</Window>
我尝试将 ItemPanel 模板化为 VirtualizingStackPanel
,但也无济于事。
我删除了扩展器部分,因为它不相关。您可以双击父节点展开树,加载子节点需要很长时间。
在 TreeListView 样式上,在 ItemsPresenter 的父 ScrollViewer 上,设置 CanContentScroll="True":
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
在 TreeListViewItem 样式中,您需要有一个名为 "Expander" 的东西(出于某种我不知道的原因 - 也许某些 style/code 正在寻找它?)。
只需在样式的 StackPanel 中放置一个 ToggleButton:
<StackPanel>
<ToggleButton x:Name="Expander" Width="0" />
<Border Name="Bd" ... />
....
</StackPanel>
这是完整的 XAML:
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication88"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TreeListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListViewItem">
<StackPanel>
<ToggleButton x:Name="Expander" Width="0" />
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}" >
</GridViewRowPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:TreeListView ItemsSource="{Binding Nodes}" VirtualizingPanel.IsVirtualizing="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"/>
</TreeView.ItemTemplate>
<local:TreeListView.Columns>
<GridViewColumn Header="Test" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Test 2" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data2}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
</local:TreeListView>
</Grid>
</Window>
我添加了一个 Test2 DataViewColum 并向节点 class 添加了一个 Data2 属性 以确保它有效(确实有效)。以下是对代码的更改:
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; private set; }
public ViewModel()
{
Nodes = new ObservableCollection<Node>();
Node parent = new Node("Parent", "Parent2");
for (int i = 0; i < 5000; i++)
parent.Children.Add(new Node(i.ToString(), (i * i).ToString()));
Nodes.Add(parent);
}
}
public class Node
{
public string Data { get; set; }
public string Data2 { get; set; }
public List<Node> Children { get; set; }
public Node(string data, string data2)
{
Data = data;
Data2 = data2;
Children = new List<Node>();
}
}
仅供参考 - VS 为我制作的模板(当我正在弄清楚这一点时),将 CanContentScroll="True" 放入 VirtualizingPanel.IsVirtualizing 触发器的 setter 中是真的。像这样:
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer x:Name="_tv_scrollviewer_" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="VirtualizingPanel.IsVirtualizing" Value="True">
<Setter Property="CanContentScroll" TargetName="_tv_scrollviewer_" Value="True"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我制作了一个包含多列的自定义 TreeView。一切都很好,直到树中有很多项目。
我尝试通过 VirtualizingPanel.IsVirtualizing="True"
启用虚拟化(如果您是 < .NET 4.5,将是 VirtualizingStackPanel.IsVirtualizing
)但它不仅没有加速,它实际上使加载时间均匀更糟。
在普通的 TreeView 上,这个 属性 可以解决问题,但我找不到让它在我的自定义 Tree
TreeViewItem.cs
public class TreeListViewItem : TreeViewItem
{
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
}
TreeListView.cs
public class TreeListView : TreeView
{
public GridViewColumnCollection Columns { get; set; }
public TreeListView()
{
Columns = new GridViewColumnCollection();
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
}
Node.cs
public class Node
{
public string Data { get; set; }
public List<Node> Children { get; set; }
public Node(string data)
{
Data = data;
Children = new List<Node>();
}
}
ViewModel.cs
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; private set; }
public ViewModel()
{
Nodes = new ObservableCollection<Node>();
Node parent = new Node("Parent");
for (int i = 0; i < 5000; i++)
parent.Children.Add(new Node(i.ToString()));
Nodes.Add(parent);
}
}
MainWindow.xaml
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFTest"
Title="Test"
mc:Ignorable="d"
Width="200">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TreeListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListViewItem">
<StackPanel>
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}" >
</GridViewRowPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:TreeListView ItemsSource="{Binding Nodes}" VirtualizingPanel.IsVirtualizing="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"/>
</TreeView.ItemTemplate>
<local:TreeListView.Columns>
<GridViewColumn Header="Test" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
</local:TreeListView>
</Grid>
</Window>
我尝试将 ItemPanel 模板化为 VirtualizingStackPanel
,但也无济于事。
我删除了扩展器部分,因为它不相关。您可以双击父节点展开树,加载子节点需要很长时间。
在 TreeListView 样式上,在 ItemsPresenter 的父 ScrollViewer 上,设置 CanContentScroll="True":
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
在 TreeListViewItem 样式中,您需要有一个名为 "Expander" 的东西(出于某种我不知道的原因 - 也许某些 style/code 正在寻找它?)。 只需在样式的 StackPanel 中放置一个 ToggleButton:
<StackPanel>
<ToggleButton x:Name="Expander" Width="0" />
<Border Name="Bd" ... />
....
</StackPanel>
这是完整的 XAML:
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication88"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TreeListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListViewItem">
<StackPanel>
<ToggleButton x:Name="Expander" Width="0" />
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}" >
</GridViewRowPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:TreeListView ItemsSource="{Binding Nodes}" VirtualizingPanel.IsVirtualizing="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"/>
</TreeView.ItemTemplate>
<local:TreeListView.Columns>
<GridViewColumn Header="Test" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Test 2" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data2}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
</local:TreeListView>
</Grid>
</Window>
我添加了一个 Test2 DataViewColum 并向节点 class 添加了一个 Data2 属性 以确保它有效(确实有效)。以下是对代码的更改:
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; private set; }
public ViewModel()
{
Nodes = new ObservableCollection<Node>();
Node parent = new Node("Parent", "Parent2");
for (int i = 0; i < 5000; i++)
parent.Children.Add(new Node(i.ToString(), (i * i).ToString()));
Nodes.Add(parent);
}
}
public class Node
{
public string Data { get; set; }
public string Data2 { get; set; }
public List<Node> Children { get; set; }
public Node(string data, string data2)
{
Data = data;
Data2 = data2;
Children = new List<Node>();
}
}
仅供参考 - VS 为我制作的模板(当我正在弄清楚这一点时),将 CanContentScroll="True" 放入 VirtualizingPanel.IsVirtualizing 触发器的 setter 中是真的。像这样:
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer x:Name="_tv_scrollviewer_" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="VirtualizingPanel.IsVirtualizing" Value="True">
<Setter Property="CanContentScroll" TargetName="_tv_scrollviewer_" Value="True"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>