C# 使用数据绑定时ListBox的填充速度非常慢
我以前使用代码隐藏将项目手动添加到我的列表框中,但速度非常慢。我听说,从性能方面来说,通过XAML进行数据绑定是一种可行的方法 因此,我设法让数据绑定工作起来(对绑定来说是新的),但令我沮丧的是,性能并不比我以前的非数据绑定方法好多少 我的想法是,我的列表框包含一个下面有名称的图像。我做了一些基准测试,54个项目花了整整8秒钟才显示出来。对于用户来说,等待的时间自然太长了 源映像的最大值为:2100x1535px,每个文件的范围为400kb>4mb 复制此问题所需的图像可以在此处找到:链接已删除,因为问题已得到回答,并且我的服务器没有太多带宽余量。其他图片来源: 我已经为下面的问题做了一个可复制的例子。我做错了什么使事情变得如此缓慢 多谢各位 XAML:C# 使用数据绑定时ListBox的填充速度非常慢,c#,wpf,xaml,data-binding,listbox,C#,Wpf,Xaml,Data Binding,Listbox,我以前使用代码隐藏将项目手动添加到我的列表框中,但速度非常慢。我听说,从性能方面来说,通过XAML进行数据绑定是一种可行的方法 因此,我设法让数据绑定工作起来(对绑定来说是新的),但令我沮丧的是,性能并不比我以前的非数据绑定方法好多少 我的想法是,我的列表框包含一个下面有名称的图像。我做了一些基准测试,54个项目花了整整8秒钟才显示出来。对于用户来说,等待的时间自然太长了 源映像的最大值为:2100x1535px,每个文件的范围为400kb>4mb 复制此问题所需的图像可以在此处找到:链接已删除
背后的代码:
使用系统;
使用System.Collections.Generic;
使用System.Collections.ObjectModel;
使用系统组件模型;
使用System.Linq;
使用System.Windows;
使用System.Windows.Threading;
命名空间WpfApplication1
{
///
///MainWindow.xaml的交互逻辑
///
公共部分类主窗口:窗口
{
内部类项:INotifyPropertyChanged
{
公共项(字符串名称=null)
{
this.Name=Name;
}
公共字符串名称{get;set;}
公共字符串ImagePath{get;set;}
公共事件属性更改事件处理程序属性更改;
私有void NotifyPropertyChanged(字符串propertyName)
{
if(PropertyChanged!=null)
{
PropertyChanged(这是新的PropertyChangedEventArgs(propertyName));
}
}
}
可观察收集项目收集;
列出数据;
公共主窗口()
{
初始化组件();
this.data=新列表();
this.ItemsCollection=新的ObservableCollection();
this.listBoxItems.ItemsSource=this.ItemsCollection;
对于(int i=0;i<49;i++)
{
Item newItem=新项目
{
ImagePath=String.Format(@“Images/{0}.jpg”,i+1),
Name=“项目:”+i
};
此.data.Add(newItem);
}
foreach(this.data.Select((value,i)=>new{i,value})中的var项)
{
Dispatcher.Invoke(新操作(()=>
{
this.ItemsCollection.Add(item.value);
}),DispatcherPriority.Background);
}
}
}
}
- 移动行
this.listBoxItems.ItemsSource=this.ItemsCollection方法末尾的代码>应该会有一点帮助李>
- 这里发生的事情是,每次执行
时,列表都试图更新其内容,这涉及大量I/O(读取磁盘文件并解码相当大的图像)。运行探查器应该可以确认这一点this.data.Add(newItem)
- 更好的方法是从a加载(这将需要更少的I/O),如果这对您的需求是可行的
- 启用将有助于降低内存需求
- 这里发生的事情是,每次执行
- 当两者都保留相同的对象时,您不需要
和可观察集合
。删除列表
字段数据
- 您没有正确使用
。默认情况下,ListBox将显示其项。我无法理解为什么要使用WrapPanel作为ItemsPanel,因为您将HorizontalScrollBar设置为禁用。从最小的变化开始。我的意思是,首先移除虚拟化StackPanel
和virtualzingstackpanel
,看看性能如何变化。您可以稍后更改ItemsPanel等ItemsPanel
- 我无法理解您为什么要使用
来填充Dispatcher.Invoke
。您已经在当前线程中创建了它。没必要那样。虚拟化将负责加载映像ObservableCollection
如果有问题,请告诉我。现在我可以看到您使用的图像,我可以确认这里的主要问题只是加载大型图像的基本成本。使用这些图像文件根本无法在这段时间内进行改进 您可以做的是异步加载图像,以便在用户等待加载所有图像时,程序的其他部分至少能够响应,或者减小图像的大小,以便加载速度更快。如果可能的话,我强烈推荐后者 如果出于某种原因,需要以原始的大尺寸格式部署和加载映像,那么您至少应该异步加载它们。有很多不同的方法来实现这一点 最简单的方法是在
图像上设置绑定.IsAsync
。Source
绑定:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Width="278" Height="178" Source="{Binding ImagePath, IsAsync=True}"/>
<TextBlock Text="{Binding Name}" FontSize="16"
VerticalAlignment="Bottom" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
C#:
<Window x:Class="TestSO42639506PopulateListBoxImages.MainWindow"
x:ClassModifier="internal"
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:l="clr-namespace:TestSO42639506PopulateListBoxImages"
mc:Ignorable="d"
WindowState="Maximized"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="{Binding TotalSeconds, StringFormat=Total seconds: {0:0}}"/>
<TextBlock Text="{Binding LoadSeconds, StringFormat=Load seconds: {0:0.000}}"/>
</StackPanel>
<ListBox x:Name="listBoxItems" ItemsSource="{Binding Data}"
Grid.Row="1"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Width="278" Height="178" Source="{Binding Bitmap}"/>
<TextBlock Text="{Binding Name}" FontSize="16"
VerticalAlignment="Bottom" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdatePropertyField<T>(
ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return;
}
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class Item : NotifyPropertyChangedBase
{
private string _name;
private string _imagePath;
private BitmapSource _bitmap;
public string Name
{
get { return _name; }
set { _UpdatePropertyField(ref _name, value); }
}
public string ImagePath
{
get { return _imagePath; }
set { _UpdatePropertyField(ref _imagePath, value); }
}
public BitmapSource Bitmap
{
get { return _bitmap; }
set { _UpdatePropertyField(ref _bitmap, value); }
}
}
class MainWindowModel : NotifyPropertyChangedBase
{
public MainWindowModel()
{
_RunTimer();
}
private async void _RunTimer()
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
await Task.Delay(1000);
TotalSeconds = sw.Elapsed.TotalSeconds;
}
}
private ObservableCollection<Item> _data = new ObservableCollection<Item>();
public ObservableCollection<Item> Data
{
get { return _data; }
}
private double _totalSeconds;
public double TotalSeconds
{
get { return _totalSeconds; }
set { _UpdatePropertyField(ref _totalSeconds, value); }
}
private double _loadSeconds;
public double LoadSeconds
{
get { return _loadSeconds; }
set { _UpdatePropertyField(ref _loadSeconds, value); }
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
partial class MainWindow : Window
{
private readonly MainWindowModel _model = new MainWindowModel();
public MainWindow()
{
DataContext = _model;
InitializeComponent();
_LoadItems();
}
private async void _LoadItems()
{
foreach (Item item in _GetItems())
{
_model.Data.Add(item);
}
foreach (Item item in _model.Data)
{
BitmapSource itemBitmap = await Task.Run(() =>
{
Stopwatch sw = Stopwatch.StartNew();
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
// forces immediate load on EndInit() call
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(item.ImagePath, UriKind.Relative);
bitmap.DecodePixelWidth = 278;
bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmap.EndInit();
bitmap.Freeze();
sw.Stop();
_model.LoadSeconds += sw.Elapsed.TotalSeconds;
return bitmap;
});
item.Bitmap = itemBitmap;
}
}
private static IEnumerable<Item> _GetItems()
{
for (int i = 1; i <= 60; i++)
{
Item newItem = new Item
{
ImagePath = String.Format(@"Images/{0}.jpg", i),
Name = "Item: " + i
};
yield return newItem;
}
}
}
类NotifyPropertyChangedBase:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
受保护的void\u UpdatePropertyField(
ref T字段,T值,[CallerMemberName]字符串propertyName=null)
{
如果(等于)