为什么<img source="..."/>这么慢,我该怎么办呢?

时间:2012-02-25 07:46:00

标签: wpf caching

考虑以下示例XAML文件,该文件显示Facebook的前1000人,以markz作为第4人开始。请注意,这只是一个示例。无论你如何构建任何具有1000个元素的窗口,都是一个很好的演示。

<Window x:Class="SO.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:clr="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <ListBox ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Image Source="{Binding}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Window>

背后的代码:

public partial class MainWindow : Window
{
    public MainWindow() {
        InitializeComponent();
        string[] urls = new string[1000];
        for (int i = 0; i < 1000; ++i) {
            urls[i] = "http://graph.facebook.com/" + i + "/picture";
        }
        this.DataContext = urls;
    }
}

在非常合理的桌面和高速连接上,程序非常慢。尝试使用ScrollBar滚动...说到中间,需要30秒。点击“主页”和“结束”键将花费大量时间。

这不是第一次只能获取图像到缓存的问题。来回看看已经呈现的图片有点快,但通常很慢。似乎没有任何东西存储在缓存中,关闭应用程序并重新启动它,一切都很慢。

等效的HTML代码快速消失。有些慢,第一次,但一切都很快。

发生了什么事?元素是否使用任何缓存?列表是否对当前未显示的图像进行预取?无论如何要告诉它吗?真的,我唯一的解决方案是自己管理Bitmap对象,以及缓存和预取逻辑吗?如果是这样,我可以合并以前的任何工作?

编辑(摘要):

  1. @ H.B。关闭虚拟化的答案会给你最好的结果。一旦窗口加载,就会呈现整个列表框,并且不会重新计算图像
  2. @Phil代码效果很好,它可以提高性能,尤其是来回时。
  3. 如果没有任何其他代码,WPF将不会在调用之间缓存图像。 WinINET缓存使用 NOT 。虽然HTTP Header中的请求附带了Cache指令,但WPF对它没有任何作用。

2 个答案:

答案 0 :(得分:6)

ListBoxes默认情况下虚拟化项目,因此如果向下滚动,则会动态创建项目。首先,它需要下载图像,然后对其进行解码。如果您滚动浏览了所有图像,它们可能会被缓存,但ListBox仍将重新创建Image控件,因此每次都需要再次解码图像。

您可以通过将false设置为ListBox Recycling来关闭虚拟化,然后立即加载所有内容,或者您​​可以将VirtualizingStackPanel.IsVirtualizing attached property更改为{{ 1}},然后Images(并包含ListBoxItems)一旦创建就不会被丢弃。

答案 1 :(得分:3)

另一种方法是添加自己的图像缓存,以便只下载一次图像。

使用我的示例,您可以将其放在构造函数中

this.DataContext = new ViewModel();

以下类将存储url,然后在首次访问Image属性时下载图像。

public class CachingImage
{
    private readonly Uri _uri;
    public CachingImage(string uriString)
    {
        _uri = new Uri(uriString, UriKind.RelativeOrAbsolute);
    }

    private BitmapImage _image;

    public ImageSource Image
    {
        get
        {
            if (_image == null)
            {
                _image = new BitmapImage(_uri);
                _image.DownloadCompleted += (sender, args) => ((BitmapImage)sender).Freeze();
            }

            return _image;
        }
    }
}

这是视图模型

public class ViewModel
{
    public ViewModel()
    {
        Images = Enumerable.Range(1, 1000).Select(i => new CachingImage("http://graph.facebook.com/" + i + "/picture"));
    }

    public IEnumerable<CachingImage> Images { get; private set; }
    ...

当然你需要稍微改变你的xaml

<ListBox ItemsSource="{Binding}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding Image}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>