2012-02-25 55 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