一个基于TreeView实现的树形表格控件,开启虚拟化后,在展开子节点时还是渲染子节点下的所有数据。我在视觉树上看到,展开子节点的瞬间会产生几千个子项(此时界面卡死),之后子项的数量又变成了几百个(此时界面正常),有什么办法可以不让它一次渲染所有子项。
TreeListView
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeListView}">
<Border
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ScrollViewer x:Name="PART_HeadScrollViewer" HorizontalScrollBarVisibility = "Hidden" VerticalScrollBarVisibility="Visible" Grid.Row="0">
<GridViewHeaderRowPresenter Columns="{TemplateBinding Columns}" />
</ScrollViewer>
<ScrollViewer x:Name="PART_ContentScrollViewer" ScrollViewer.IsDeferredScrollingEnabled="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible" Grid.Row="1">
<ItemsPresenter />
</ScrollViewer>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.CacheLength="2">
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>```
TreeListViewItem
```<Style TargetType="{x:Type local:TreeListViewItem}">
<Setter Property="FontSize" Value="13" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeListViewItem}">
<StackPanel>
<Border
Height="{Binding Path=ItemHeight, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}"
Margin="0"
Padding="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
BorderBrush="White"
BorderThickness="0,0,0,1"
SnapsToDevicePixels="True">
<Border
x:Name="innerBorder"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="White"
BorderThickness="0,0,0,1"
SnapsToDevicePixels="True">
<GridViewRowPresenter
x:Name="PART_Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}"
Content="{TemplateBinding Header}" />
<!--Columns="{StaticResource gvColumns}"-->
</Border>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger SourceName="innerBorder" Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
<Setter TargetName="innerBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
<Setter TargetName="innerBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
</Trigger>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true" />
<Condition Property="IsSelectionActive" Value="false" />
</MultiTrigger.Conditions>
<Setter TargetName="innerBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
</MultiTrigger>
<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="HasHeader" Value="false" />
<Condition Property="Height" Value="Auto" />
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header" Property="MinHeight" Value="19" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>```
TreeViewItem后台代码
```public class TreeListViewItem: TreeViewItem
{
public TreeListViewItem()
{
//this.ItemsPanel = new ItemsPanelTemplate(new FrameworkElementFactory(typeof(TreeListViewVirtualizingPanel)));
this.ItemsPanel = new ItemsPanelTemplate(new FrameworkElementFactory(typeof(VirtualizingStackPanel)));
}
// 计算当前节点的深度
private int level = -1;
public int Level
{
get
{
if (level == -1)
{
TreeListViewItem parent = ItemsControl.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;
}
}```
为了在WPF中使用TreeView控件实现虚拟化并避免在展开子节点时渲染所有子项,可以尝试以下几个步骤:
xml
Copy code
<TreeView ItemsSource="{Binding YourItemsSource}">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.CacheLength="2"/>
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>
2. 确保子节点也支持虚拟化
在TreeViewItem的样式中也确保ItemsPanelTemplate使用VirtualizingStackPanel:
xml
Copy code
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.CacheLength="2"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
3. 使用HierarchicalDataTemplate
使用HierarchicalDataTemplate来绑定子项,并确保虚拟化属性正确设置:
xml
Copy code
<TreeView ItemsSource="{Binding YourItemsSource}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:YourItemType}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.CacheLength="2"/>
</ItemsPanelTemplate>
</HierarchicalDataTemplate.ItemsPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
4. 自定义TreeView控件
如果上述方法仍然导致性能问题,可以自定义TreeView控件来强制实现虚拟化。以下是一个示例:
csharp
Copy code
public class VirtualizingTreeView : TreeView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new VirtualizingTreeViewItem();
}
}
public class VirtualizingTreeViewItem : TreeViewItem
{
static VirtualizingTreeViewItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(VirtualizingTreeViewItem), new FrameworkPropertyMetadata(typeof(VirtualizingTreeViewItem)));
}
public VirtualizingTreeViewItem()
{
this.ItemsPanel = new ItemsPanelTemplate(new FrameworkElementFactory(typeof(VirtualizingStackPanel)));
}
protected override DependencyObject GetContainerForItemOverride()
{
return new VirtualizingTreeViewItem();
}
}
在XAML中使用自定义的VirtualizingTreeView:
xml
Copy code
<local:VirtualizingTreeView ItemsSource="{Binding YourItemsSource}">
local:VirtualizingTreeView.Resources
<HierarchicalDataTemplate DataType="{x:Type local:YourItemType}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</local:VirtualizingTreeView.Resources>
</local:VirtualizingTreeView>
总结
通过确保TreeView及其子项使用VirtualizingStackPanel来实现虚拟化,并使用HierarchicalDataTemplate绑定子项,可以显著提高性能,避免在展开节点时一次性渲染所有子项。如果上述方法仍无法解决性能问题,可以考虑自定义TreeView控件来强制实现虚拟化。