我遇到了 ListView
的主要性能问题每当我实现分组时。我找到了 somewhat similar questions在 StackOverflow 上,但似乎没有任何帮助!
这是我目前的情况(我已经简化了我的项目,所以噪音更少):
我有一个 ContentControl
用ListView
作为 child 。 ListView
绑定(bind)到 ObservableCollection
,最初是空的。随着时间的流逝,对象被添加到集合中(在本例中,使用 DispatcherTimer
每 10 秒添加 500 个项目)。 ObservableCollection
的大小会有所不同,但该系列最终可能会超过 25,000 件。
当 ObservableCollection
小于 2000(不是确切数字),列大小调整如下所示:
但是,随着更多对象被添加到 ObservableCollection
,性能明显下降(您需要向下滚动才能发生这种情况)。
这最终会导致 Application
锁定。
我认为可以使用 Virtualization
解决问题,所以我尝试使用以下内容:
<ListView x:Name="ListView1"
Style="{DynamicResource lvStyle}"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsContainerVirtualizable="True"
ScrollViewer.IsDeferredScrollingEnabled="True">
然而,似乎没有任何效果!
更不用说,VirtualizingPanel.IsVirtualizingWhenGrouping="True"
导致 ListView
完全锁定。
我也调查了Paul McClean's excellent Data Virtualization ,但是,它不处理分组。
问题:在 ListView
中对项目进行分组时,有没有办法在不显着影响应用程序性能的情况下调整列的大小?
理想情况下,我想减少内存开销,所以我完全赞成实现某种异步解决方案。
代码:
XAML:
<ContentControl x:Class="ListViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ListViewDemo"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:dat="clr-namespace:System.Windows.Data;assembly=PresentationFramework"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Classic"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="300">
<ContentControl.Resources>
<Style x:Key="lvStyle" TargetType="{x:Type ListView}">
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="True"/>
<Setter Property="ListView.ItemsSource" Value="{Binding}"/>
<Setter Property="ListView.View">
<Setter.Value>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Date">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Desc">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Desc}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</Setter.Value>
</Setter>
</Style>
</ContentControl.Resources>
<Grid>
<ListView x:Name="ListView1"
Style="{DynamicResource lvStyle}"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsContainerVirtualizable="True"
ScrollViewer.IsDeferredScrollingEnabled="True">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Border DockPanel.Dock="Top">
<TextBlock x:Name="groupItem"
Text="{Binding ItemCount, StringFormat={}({0} Results)}"></TextBlock>
</Border>
<ItemsPresenter DockPanel.Dock="Bottom"></ItemsPresenter>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</ContentControl>
代码隐藏:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace ListViewDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : ContentControl
{
private ObservableCollection<Event> eventCollection = new ObservableCollection<Event>();
public MainWindow()
{
InitializeComponent();
DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 10);
dispatcherTimer.Start();
ListView1.ItemsSource = eventCollection;
ListView1.Items.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Descending));
ListView1.Items.SortDescriptions.Add(new SortDescription("Date", ListSortDirection.Descending));
ListView1.Items.GroupDescriptions.Add(new PropertyGroupDescription("Seconds"));
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
for(var i = 0; i < 500; i++){
eventCollection.Add(new Event
{
Name = string.Format("Name_{0}", eventCollection.Count),
Date = DateTime.Now.ToString("MM.dd.yy HH:mm"),
Seconds = Convert.ToInt32(DateTime.Now.ToString("ss")),
Desc = "Description"
});
}
}
public class Event
{
public string Name { get; set; }
public string Date { get; set; }
public int Seconds { get; set; }
public string Desc { get; set; }
}
}
}
最佳答案
您的性能问题是由于未使用 IsVirtualizingWhenGrouping
您提到使用 IsVirtualizingWhenGrouping
会锁定您的应用程序,这是一个已知的 WPF 问题(有关使用自定义 GroupStyle 和 IsVirtualizingWhenGrouping 时发生的此错误的更多信息,请参阅:http://connect.microsoft.com/VisualStudio/feedback/details/780146/freeze-when-using-virtualizingpanel-isvirtualizingwhengrouping
设置为 true)
这里有一个快速解决问题的方法:您只需在 GroupStyle ControlTemplate 中添加一个扩展器。然后,您将能够使用 IsVirtualizingWhenGrouping
,然后在滚动/调整列大小时获得良好的性能。
这是在我的机器上运行的代码:(我将所有内容直接放在 MainWindow 中以简化一点)
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="lvStyle" TargetType="{x:Type ListView}" >
<Setter Property="ListView.ItemsSource" Value="{Binding}"/>
<Setter Property="ListView.View">
<Setter.Value>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Date">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Desc">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Desc}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListView x:Name="ListView1"
Style="{DynamicResource lvStyle}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<DockPanel>
<Border DockPanel.Dock="Top">
<TextBlock x:Name="groupItem"
Text="{Binding ItemCount, StringFormat={}({0} Results)}"></TextBlock>
</Border>
<ItemsPresenter x:Name="groupItemPresenter" DockPanel.Dock="Bottom"></ItemsPresenter>
</DockPanel>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</Window>
编辑:这是一个“隐藏”扩展器的 ControlTemplate。这是我删除了不必要部分的原始版本:
<ControlTemplate x:Key="CustomExpanderControlTemplate" TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="True">
<DockPanel>
<ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<ToggleButton.FocusVisualStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0" SnapsToDevicePixels="True" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.FocusVisualStyle>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
<ContentPresenter x:Name="ExpandSite" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" DockPanel.Dock="Bottom" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="Transparent" HorizontalAlignment="Center" Height="19" Stroke="DarkGray" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF666666" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C7FB1"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF222222"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF526C7B"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF003366"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="DarkGray"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF666666"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="180"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="Transparent" HorizontalAlignment="Center" Height="19" Stroke="DarkGray" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF666666" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C7FB1"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF222222"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF526C7B"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF003366"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="DarkGray"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF666666"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="Transparent" HorizontalAlignment="Center" Height="19" Stroke="DarkGray" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF666666" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C7FB1"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF222222"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF526C7B"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF003366"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="DarkGray"/>
<Setter Property="Fill" TargetName="circle" Value="Transparent"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF666666"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
你可以通过在组样式中设置它来使用它:
<Expander IsExpanded="True" Template="{DynamicResource CustomExpanderControlTemplate}">
关于c# - ListView 调整列大小性能问题(分组),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16245194/