2012-02-25 51 views
7

考虑以下示例XAML文件,其中显示Facebook的前1000人,以markz作为第4人开始。请注意,这只是一个示例。任何有1000个元素的窗口,无论你如何构建它,都是一个很好的示范。为什么<Image Source ='...'>这么慢,我能做些什么呢?

<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对象,还有缓存和预取逻辑?如果是这样,以前的任何工作我可以合并?

EDIT(摘要):

  1. @ H.B。答案关闭虚拟化会给你最好的结果。整个列表框会在窗口加载后立即生成,并且不会重新计算图像
  2. @Pil代码效果很好,它可以提高性能,特别是在来回时。
  3. 没有任何额外的代码,WPF不会在调用之间缓存图像。 WinINET缓存使用的是而不是。尽管请求在HTTP Header中带有Cache指令,但WPF并没有执行任何操作。

回答

6

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

你可以通过在ListBox然后一切VirtualizingStackPanel.IsVirtualizing attached property设置为false会马上被加载关闭虚拟化,或者你可以改变VirtualizationModeRecycling,那么Images(以及含有ListBoxItems)将不会被扔掉,一旦创建。

+0

谢谢H.B. +1并接受。 – Uri 2012-02-26 06:52:28

3

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

使用我的例子中,你会把这在构造函数

this.DataContext = new ViewModel(); 

下面的类将存储的URL,然后下载图像时第一次被访问的图像属性。

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> 
+1

谢谢@菲尔。 +1。这是一个接受的要求。 – Uri 2012-02-26 06:52:52