我正在尝试制作一个显示来自互联网图片的列表框。这些项目是通过将itemsource绑定到包含图像URL和其他一些属性(标题,desc等等)的模型来提供的。
不幸的是,列表加载速度非常慢,因为WPF在显示列表之前尝试从网上下载所有图片,它会使应用程序冻结15到25秒。
我已经读过我应该将图片加载到另一个帖子中,但我不知道应该在哪里做以及如何做?是否更好地直接在模型中加载所有图片(通过仅为此创建一个线程池 - 但问题是它不是模型/模型视图的真正部分)或者更好地创建一个将直接更新的后台线程列表何时有数据?
谢谢!
答案 0 :(得分:7)
简单的方法就是像这样设置Binding.IsAsync
属性:
<Image ImageSource="{Binding propertyThatComputesImageSource, IsAsync=true}" />
每次访问propertyThatComputesImageSource
都将从ThreadPool线程完成。如果线程使用ImageCacheOptions.OnLoad创建图像,它将阻塞,直到加载图像。因此,UI将立即启动,图像将在后台加载,并在可用时显示。
Binding.IsAsync
对于10或20张图片来说是一个很好的解决方案,但如果你有数百张图片并且加载延迟很长,则可能不是一个好的解决方案,因为你最终可能会有数百个线程。在这种情况下,直接使用ThreadPool将数据加载到数据绑定之外:
ThreadPool.QueueUserWorkItem((state) =>
{
foreach(var model in _models.ToArray())
model.ImageSource = LoadOneImage(model.ImageUrl);
});
如果模型的属性是DependencyProperty,则可能需要使用Dispatcher.Invoke或者两个扩展,因为无法从单独的线程访问它们。
这项技术可以扩展为产生固定数量的工作人员来加载图像并打破它们之间的工作,因此会发生多次图像下载,但是同时下载的数量是有限的,所以你最终不会有数百个线程。
答案 1 :(得分:0)
一种非常简单的方法是在视图模型中使用System.ComponentModel.BackgroundWorker
(more info)。这是一个简单的例子:
using (BackgroundWorker bg = new BackgroundWorker())
{
bg.DoWork += (sender, args) => FetchImages(viewModelObjectsNeedingImages);
bg.RunWorkerAsync();
}
BackgroundWorker
也可以非常方便地取消后台任务。
您可能还想查看UI virtualization。
答案 2 :(得分:0)
您可以使用this asynchronous observable collection将数据源绑定到ListBox,并且仍然可以在另一个线程中加载数据。
有关如何编写此类线程的示例,请查看BackgroundWorker文档。
此外,您可能需要考虑延迟加载图像,即只加载可见的图像,并随时加载更多图像。这样,您可以获得两个好处:在获取线程中的图像时不必阻止UI,并且您可以重复使用您的集合,一次只能保存一些图像,从而防止在内存中填充大量图像一旦你计划展示,比如,几千。请查看here,了解有关如何实施此类虚拟化的详细信息。
答案 3 :(得分:0)
感谢大家!
所有解决方案都应该工作:)在我的情况下,在ListBoxItem的图像上使用IsAsync是足够好的(最多有50个项目)。实际上,它并没有从占用太多时间的网络中检索图像!
不幸的是我的问题出在其他地方......这与.NET 3.5中的代理检测错误有关,导致应用程序加载非常缓慢:
如果应用程序文件夹中没有任何带有以下代码的your_application_name.exe.config文件 - .NET可能需要花费大量时间来检测在第一次访问网络时冻结应用程序的代理:
<configuration>
<system.net>
<defaultProxy enabled="false"/>
</system.net>
</configuration>