带列的 TreeView 速度慢
TreeView with Columns slow
我想在我的 WPF 应用程序中显示按列拆分的大型层次结构。我的意图是使用带列的 TreeView 来实现。
对于少量数据,控件工作正常。滚动流畅,加载时间也可以接受(<1s)。但是随着大量数据滚动开始挂起并且不再流畅并且加载需要几秒钟。
这里是数据结构的代码:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Contact> Contacts { get; set; }
}
public class Contact:Person
{
public bool Test { get; set; }
}
这里是使用转换器的代码:
public class LevelToIndentConverter : IValueConverter
{
private static readonly LevelToIndentConverter DefaultInstance = new LevelToIndentConverter();
public static LevelToIndentConverter Default
{
get { return DefaultInstance; }
}
private const double IndentSize = 20.0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new Thickness((int)value * IndentSize, 0, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
TreeListView代码:
public class TreeListView : TreeView
{
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView), new FrameworkPropertyMetadata(new GridViewColumnCollection(), FrameworkPropertyMetadataOptions.None));
public GridViewColumnCollection Columns
{
get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
public TreeListView()
{
DefaultStyleKey = typeof(TreeListView);
Columns = new GridViewColumnCollection();
}
}
public class TreeListViewItem : TreeViewItem
{
private int level = -1;
public int Level
{
get
{
if (level != -1) { return level; }
var parent = ItemsControlFromItemContainer(this) as TreeListViewItem;
level = (parent != null) ? parent.Level + 1 : 0;
return level;
}
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
public TreeListViewItem()
{
DefaultStyleKey = typeof(TreeListViewItem);
}
MainWindow.xaml
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfApplication4"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="ToggleButton.TreeExpander" TargetType="{x:Type ToggleButton}">
<Setter Property="Margin" Value="{Binding Level, RelativeSource={RelativeSource AncestorType={x:Type controls:TreeListViewItem}}, Converter={x:Static controls:LevelToIndentConverter.Default}}"/>
<Setter Property="IsChecked" Value="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType= {x:Type controls:TreeListViewItem}}}"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Padding" Value="0,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid x:Name="ArrowGrid" Grid.Column="0" Width="20">
<ContentControl x:Name="Up_Arrow" VerticalAlignment="Center" HorizontalAlignment="Center" Focusable="False">
<TextBlock Text="-"/>
</ContentControl>
<ContentControl x:Name="Down_Arrow" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed" Focusable="False">
<TextBlock Text="+"></TextBlock>
</ContentControl>
</Grid>
<ContentPresenter Grid.Column="1" HorizontalAlignment="Stretch" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=HasItems,RelativeSource={RelativeSource AncestorType={x:Type controls:TreeListViewItem}}}" Value="False">
<Setter TargetName="ArrowGrid" Property="Visibility" Value="Hidden"/>
<Setter Property="IsHitTestVisible" Value="False"/>
</DataTrigger>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="Down_Arrow" Property="Visibility" Value="Visible" />
<Setter TargetName="Up_Arrow" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeListViewScrollViewerStyle" TargetType="{x:Type ScrollViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Grid Background="{TemplateBinding Background}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Margin="{TemplateBinding Padding}">
<ScrollViewer DockPanel.Dock="Top" Focusable="false"
HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<GridViewHeaderRowPresenter Columns="{Binding Path=TemplatedParent.Columns, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderContainerStyle="{Binding Path=TemplatedParent.ColumnHeaderContainerStyle, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderTemplate="{Binding Path=TemplatedParent.ColumnHeaderTemplate, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderTemplateSelector="{Binding Path=TemplatedParent.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource TemplatedParent}}"
AllowsColumnReorder="{Binding Path=TemplatedParent.AllowsColumnReorder, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderContextMenu="{Binding Path=TemplatedParent.ColumnHeaderContextMenu, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderToolTip="{Binding Path=TemplatedParent.ColumnHeaderToolTip, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
<ScrollContentPresenter Name="PART_ScrollContentPresenter"
KeyboardNavigation.DirectionalNavigation="Local"
CanContentScroll="{TemplateBinding CanContentScroll}"
CanHorizontallyScroll="False"
CanVerticallyScroll="False" />
</DockPanel>
<ScrollBar Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
BorderThickness="0,1,0,0"
Grid.Row="1" Grid.Column="0"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{TemplateBinding HorizontalOffset}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
<ScrollBar Name="PART_VerticalScrollBar"
Grid.Column="1" Grid.Row="0"
BorderThickness="1,0,0,0"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{TemplateBinding VerticalOffset}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeListView.Base" TargetType="{x:Type controls:TreeListView}" >
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Item" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ScrollViewer Style="{StaticResource TreeListViewScrollViewerStyle}">
<ItemsPresenter x:Name="ItemsPresenter"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeListViewItem.Base" TargetType="{x:Type controls:TreeListViewItem}" >
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:TreeListViewItem}">
<VirtualizingStackPanel>
<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 Columns, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:TreeListView}}" />
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</VirtualizingStackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasHeader" Value="false"/>
<Condition Property="Width" Value="Auto"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header" Property="MinWidth" Value="75"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gray"/>
<Setter Property="Foreground" Value="White"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource TreeListView.Base}" TargetType="{x:Type controls:TreeListView}" />
<Style BasedOn="{StaticResource TreeListViewItem.Base}" TargetType="{x:Type controls:TreeListViewItem}" />
</Window.Resources>
<Grid>
<controls:TreeListView HorizontalAlignment="Stretch" ItemsSource="{Binding}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Contacts}" />
</TreeView.ItemTemplate>
<controls:TreeListView.Columns>
<GridViewColumn Header="First Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type controls:Person}">
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:Contact}">
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</DataTemplate.Resources>
<ToggleButton Content="{Binding}" Style="{StaticResource ToggleButton.TreeExpander}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Last Name" Width="300">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type controls:Person}">
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:Contact}">
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
</DataTemplate.Resources>
<ContentPresenter Content="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</controls:TreeListView.Columns>
</controls:TreeListView>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var persons = new List<Person>();
for (int i = 0; i < 1000; i++)
{
var person = new Person
{
FirstName = string.Format("{0} {1}", "Hans", i),
LastName = "Wurst",
Contacts = new List<Contact>()
};
for (int j = 0; j < 100; j++)
{
person.Contacts.Add(new Contact { FirstName = string.Format("{0} {1}", "Contact", j), LastName = "-" });
}
persons.Add(person);
}
this.DataContext = persons;
}
}
有人知道为什么这个观点滞后吗?我尝试启用虚拟化来提高性能,但它并没有解决问题。
到目前为止我发现它似乎与GridViewRowPresenter
有关。当我删除它时一切正常。
欢迎任何帮助!
非常感谢,
斯特凡
更新
已为 TreeListView
激活虚拟化。参见样式 I TreeListView.Base
。样式应用在 TreeListView
by line
<Style BasedOn="{StaticResource TreeListView.Base}" TargetType="{x:Type controls:TreeListView}" />
。就我可以使用 WPF snoop 进行检查而言,虚拟化工作正常。
更新 2
我已经使用此处发布的代码在 GitHub 上创建了一个示例应用程序。你可以找到它here on GitHub
它还包含一个 TreeListView
样式的实现,没有 GridRows 来说明即使加载了所有子项,一切都可以正常工作。
VirtualizingStackPanel.VirtualizationMode
和 VirtualizingStackPanel.IsVirtualizing
可以显着减少内存消耗并显着提高性能。请查看以下代码片段:
<TreeView x:Name=”myTreeView”
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling" />
更新:
也许有帮助,但尝试不在 Style
中设置属性,而是在您的自定义控件中设置属性 TreeListView
:
<controls:TreeListView HorizontalAlignment="Stretch" ItemsSource="{Binding}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Contacts}" />
</TreeView.ItemTemplate>
</controls:TreeListView>
尝试在用户展开 TreeView 时加载数据。通常,用户不需要同时在屏幕上打开 50,000 个节点。仅加载用户需要查看的子项,以及您需要正确显示的任何子项信息。
更新 1:
看起来 GridViewRowPresenter
是您的性能问题的原因。我已经使用了 ContentPresenter
并且性能变得与您的 FastTreeView
中一样,但是 Header
对他们的列不正确。你应该更正它
<Style x:Key="TreeListViewItem.Base" TargetType="{x:Type controls:TreeListViewItem}" >
...
<VirtualizingStackPanel>
<Border Name="Bd" Background="{TemplateBinding Background}" VirtualizingPanel.IsVirtualizing="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<!--<GridViewRowPresenter x:Name="PART_Header" Content="{TemplateBinding Header}" Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:TreeListView}}" />-->
<ContentPresenter x:Name="PART_Header" Content="{Binding}" Grid.Column="1">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type controls:Person}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" Margin="0,0,10,0"/>
<TextBlock Text="{Binding LastName}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:Contact}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</VirtualizingStackPanel>
我已经解决了这个问题。
看来性能问题的根本原因是GridViewRowPresenter
没有设置固定高度。
当我在 TreeListViewItem.Base
样式的 GridViewRowPresenter
上设置高度时,一切都非常有效:
<GridViewRowPresenter x:Name="PART_Header" Height="20" Content="{TemplateBinding Header}" Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:TreeListView}}" />
感谢您的帮助。
我想在我的 WPF 应用程序中显示按列拆分的大型层次结构。我的意图是使用带列的 TreeView 来实现。
对于少量数据,控件工作正常。滚动流畅,加载时间也可以接受(<1s)。但是随着大量数据滚动开始挂起并且不再流畅并且加载需要几秒钟。
这里是数据结构的代码:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Contact> Contacts { get; set; }
}
public class Contact:Person
{
public bool Test { get; set; }
}
这里是使用转换器的代码:
public class LevelToIndentConverter : IValueConverter
{
private static readonly LevelToIndentConverter DefaultInstance = new LevelToIndentConverter();
public static LevelToIndentConverter Default
{
get { return DefaultInstance; }
}
private const double IndentSize = 20.0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new Thickness((int)value * IndentSize, 0, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
TreeListView代码:
public class TreeListView : TreeView
{
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView), new FrameworkPropertyMetadata(new GridViewColumnCollection(), FrameworkPropertyMetadataOptions.None));
public GridViewColumnCollection Columns
{
get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
public TreeListView()
{
DefaultStyleKey = typeof(TreeListView);
Columns = new GridViewColumnCollection();
}
}
public class TreeListViewItem : TreeViewItem
{
private int level = -1;
public int Level
{
get
{
if (level != -1) { return level; }
var parent = ItemsControlFromItemContainer(this) as TreeListViewItem;
level = (parent != null) ? parent.Level + 1 : 0;
return level;
}
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
public TreeListViewItem()
{
DefaultStyleKey = typeof(TreeListViewItem);
}
MainWindow.xaml
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfApplication4"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="ToggleButton.TreeExpander" TargetType="{x:Type ToggleButton}">
<Setter Property="Margin" Value="{Binding Level, RelativeSource={RelativeSource AncestorType={x:Type controls:TreeListViewItem}}, Converter={x:Static controls:LevelToIndentConverter.Default}}"/>
<Setter Property="IsChecked" Value="{Binding Path=IsExpanded, RelativeSource={RelativeSource AncestorType= {x:Type controls:TreeListViewItem}}}"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Padding" Value="0,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid x:Name="ArrowGrid" Grid.Column="0" Width="20">
<ContentControl x:Name="Up_Arrow" VerticalAlignment="Center" HorizontalAlignment="Center" Focusable="False">
<TextBlock Text="-"/>
</ContentControl>
<ContentControl x:Name="Down_Arrow" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed" Focusable="False">
<TextBlock Text="+"></TextBlock>
</ContentControl>
</Grid>
<ContentPresenter Grid.Column="1" HorizontalAlignment="Stretch" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=HasItems,RelativeSource={RelativeSource AncestorType={x:Type controls:TreeListViewItem}}}" Value="False">
<Setter TargetName="ArrowGrid" Property="Visibility" Value="Hidden"/>
<Setter Property="IsHitTestVisible" Value="False"/>
</DataTrigger>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="Down_Arrow" Property="Visibility" Value="Visible" />
<Setter TargetName="Up_Arrow" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeListViewScrollViewerStyle" TargetType="{x:Type ScrollViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Grid Background="{TemplateBinding Background}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Margin="{TemplateBinding Padding}">
<ScrollViewer DockPanel.Dock="Top" Focusable="false"
HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<GridViewHeaderRowPresenter Columns="{Binding Path=TemplatedParent.Columns, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderContainerStyle="{Binding Path=TemplatedParent.ColumnHeaderContainerStyle, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderTemplate="{Binding Path=TemplatedParent.ColumnHeaderTemplate, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderTemplateSelector="{Binding Path=TemplatedParent.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource TemplatedParent}}"
AllowsColumnReorder="{Binding Path=TemplatedParent.AllowsColumnReorder, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderContextMenu="{Binding Path=TemplatedParent.ColumnHeaderContextMenu, RelativeSource={RelativeSource TemplatedParent}}"
ColumnHeaderToolTip="{Binding Path=TemplatedParent.ColumnHeaderToolTip, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
<ScrollContentPresenter Name="PART_ScrollContentPresenter"
KeyboardNavigation.DirectionalNavigation="Local"
CanContentScroll="{TemplateBinding CanContentScroll}"
CanHorizontallyScroll="False"
CanVerticallyScroll="False" />
</DockPanel>
<ScrollBar Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
BorderThickness="0,1,0,0"
Grid.Row="1" Grid.Column="0"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{TemplateBinding HorizontalOffset}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
<ScrollBar Name="PART_VerticalScrollBar"
Grid.Column="1" Grid.Row="0"
BorderThickness="1,0,0,0"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{TemplateBinding VerticalOffset}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeListView.Base" TargetType="{x:Type controls:TreeListView}" >
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Item" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ScrollViewer Style="{StaticResource TreeListViewScrollViewerStyle}">
<ItemsPresenter x:Name="ItemsPresenter"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeListViewItem.Base" TargetType="{x:Type controls:TreeListViewItem}" >
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:TreeListViewItem}">
<VirtualizingStackPanel>
<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 Columns, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:TreeListView}}" />
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</VirtualizingStackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasHeader" Value="false"/>
<Condition Property="Width" Value="Auto"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header" Property="MinWidth" Value="75"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gray"/>
<Setter Property="Foreground" Value="White"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource TreeListView.Base}" TargetType="{x:Type controls:TreeListView}" />
<Style BasedOn="{StaticResource TreeListViewItem.Base}" TargetType="{x:Type controls:TreeListViewItem}" />
</Window.Resources>
<Grid>
<controls:TreeListView HorizontalAlignment="Stretch" ItemsSource="{Binding}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Contacts}" />
</TreeView.ItemTemplate>
<controls:TreeListView.Columns>
<GridViewColumn Header="First Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type controls:Person}">
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:Contact}">
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</DataTemplate.Resources>
<ToggleButton Content="{Binding}" Style="{StaticResource ToggleButton.TreeExpander}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Last Name" Width="300">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type controls:Person}">
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:Contact}">
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
</DataTemplate.Resources>
<ContentPresenter Content="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</controls:TreeListView.Columns>
</controls:TreeListView>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var persons = new List<Person>();
for (int i = 0; i < 1000; i++)
{
var person = new Person
{
FirstName = string.Format("{0} {1}", "Hans", i),
LastName = "Wurst",
Contacts = new List<Contact>()
};
for (int j = 0; j < 100; j++)
{
person.Contacts.Add(new Contact { FirstName = string.Format("{0} {1}", "Contact", j), LastName = "-" });
}
persons.Add(person);
}
this.DataContext = persons;
}
}
有人知道为什么这个观点滞后吗?我尝试启用虚拟化来提高性能,但它并没有解决问题。
到目前为止我发现它似乎与GridViewRowPresenter
有关。当我删除它时一切正常。
欢迎任何帮助!
非常感谢, 斯特凡
更新
已为 TreeListView
激活虚拟化。参见样式 I TreeListView.Base
。样式应用在 TreeListView
by line
<Style BasedOn="{StaticResource TreeListView.Base}" TargetType="{x:Type controls:TreeListView}" />
。就我可以使用 WPF snoop 进行检查而言,虚拟化工作正常。
更新 2
我已经使用此处发布的代码在 GitHub 上创建了一个示例应用程序。你可以找到它here on GitHub
它还包含一个 TreeListView
样式的实现,没有 GridRows 来说明即使加载了所有子项,一切都可以正常工作。
VirtualizingStackPanel.VirtualizationMode
和 VirtualizingStackPanel.IsVirtualizing
可以显着减少内存消耗并显着提高性能。请查看以下代码片段:
<TreeView x:Name=”myTreeView”
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling" />
更新:
也许有帮助,但尝试不在 Style
中设置属性,而是在您的自定义控件中设置属性 TreeListView
:
<controls:TreeListView HorizontalAlignment="Stretch" ItemsSource="{Binding}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Contacts}" />
</TreeView.ItemTemplate>
</controls:TreeListView>
尝试在用户展开 TreeView 时加载数据。通常,用户不需要同时在屏幕上打开 50,000 个节点。仅加载用户需要查看的子项,以及您需要正确显示的任何子项信息。
更新 1:
看起来 GridViewRowPresenter
是您的性能问题的原因。我已经使用了 ContentPresenter
并且性能变得与您的 FastTreeView
中一样,但是 Header
对他们的列不正确。你应该更正它
<Style x:Key="TreeListViewItem.Base" TargetType="{x:Type controls:TreeListViewItem}" >
...
<VirtualizingStackPanel>
<Border Name="Bd" Background="{TemplateBinding Background}" VirtualizingPanel.IsVirtualizing="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<!--<GridViewRowPresenter x:Name="PART_Header" Content="{TemplateBinding Header}" Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:TreeListView}}" />-->
<ContentPresenter x:Name="PART_Header" Content="{Binding}" Grid.Column="1">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type controls:Person}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" Margin="0,0,10,0"/>
<TextBlock Text="{Binding LastName}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:Contact}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</VirtualizingStackPanel>
我已经解决了这个问题。
看来性能问题的根本原因是GridViewRowPresenter
没有设置固定高度。
当我在 TreeListViewItem.Base
样式的 GridViewRowPresenter
上设置高度时,一切都非常有效:
<GridViewRowPresenter x:Name="PART_Header" Height="20" Content="{TemplateBinding Header}" Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:TreeListView}}" />
感谢您的帮助。