首页 新闻 会员 周边

WPF中TreeView控件虚拟化的问题

0
悬赏园豆:50 [待解决问题]

一个基于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;
        }
    }```
在下路过的主页 在下路过 | 初学一级 | 园豆:54
提问于:2024-06-13 09:46
< >
分享
所有回答(1)
0

为了在WPF中使用TreeView控件实现虚拟化并避免在展开子节点时渲染所有子项,可以尝试以下几个步骤:

  1. 确保开启虚拟化
    在TreeView的ItemsPanel中使用VirtualizingStackPanel,并确保设置VirtualizingPanel.IsVirtualizing和VirtualizingPanel.VirtualizationMode属性:

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控件来强制实现虚拟化。

Technologyforgood | 园豆:7199 (大侠五级) | 2024-06-13 16:49
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册