2012-02-25 9 views
7

Considerando el siguiente archivo XAML de muestra, que muestra las primeras 1000 personas de Facebook, comenzando con markz como 4ta persona. Tenga en cuenta que esto es solo una muestra. Cualquier ventana con elemento 1000, sin importar cómo la construyas, es una buena demostración.¿Por qué <Origen de imagen = '...'> es tan lento y qué puedo hacer al respecto?

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

Y el código subyacente:

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; 
    } 
} 

En un escritorio muy razonable y conexión de alta velocidad, el programa es extremadamente lento. Tratar de desplazarse con ScrollBar ... decir al medio, tomará 30 segundos. Tocar las teclas 'Inicio' y 'Finalizar' llevará bastante tiempo.

No es un problema de primera vez con solo obtener imágenes en la caché. Yendo y viniendo y viendo fotos ya presentadas es algo más rápido pero generalmente muy lento. Parece que no hay nada almacenado en la memoria caché, cerrando la aplicación y reiniciándola, todo vuelve a ser lento.

El código HTML equivalente fluye rápidamente. Algo de lentitud la primera vez, pero luego todo es muy rápido.

¿Qué está pasando? ¿El elemento usa algún almacenamiento en caché? ¿La lista realiza alguna precarga de las imágenes que no se presentan actualmente? ¿Hay alguna forma de decirle que haga? ¿Es realmente que mi única solución es administrar yo mismo los objetos de mapa de bits, junto con el almacenamiento en caché y la lógica de captación previa? Si es así, ¿algún trabajo previo que pueda incorporar?

EDITAR (resumen):

  1. @ H.B. responder para desactivar la virtualización le dará el mejor resultado. Todo el cuadro de lista se representa tan pronto como se carga la ventana y no se vuelve a calcular la imagen.
  2. @El código de Phil funciona muy bien y mejora el rendimiento, especialmente al ir y venir.
  3. Sin ningún código adicional, WPF no almacenará en caché las imágenes entre la invocación. El caché WinINET es NO utilizado. Aunque la solicitud viene con instrucciones de caché en el encabezado HTTP, WPF no hace nada con eso.

Respuesta

6

ListBoxes virtualizar los elementos por defecto, por lo que si se desplaza hacia abajo los artículos son creados sobre la marcha. Al principio, necesita descargar la imagen, luego la decodifica. Si ha desplazado todas las imágenes, puede que estén en caché, pero ListBox seguirá recreando los controles Image y, por lo tanto, las imágenes deben decodificarse nuevamente cada vez.

Se puede desactivar la virtualización mediante el establecimiento de la VirtualizingStackPanel.IsVirtualizing attached property a false en el ListBox entonces todo se cargará de inmediato, o se puede cambiar el VirtualizationMode a Recycling, entonces el Images (y que contiene ListBoxItems) no se ignora, una vez creado.

+0

Gracias H. B. +1 y aceptar. – Uri

3

Una alternativa sería agregar su propio almacenamiento en caché de imágenes para que las imágenes solo se descarguen una vez.

Usando mi ejemplo, usted podría poner esto en su constructor

this.DataContext = new ViewModel(); 

La clase siguiente almacenaría la url y luego descargar la imagen cuando se accede por primera vez la propiedad de imagen.

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; 
     } 
    } 
} 

Aquí está el modelo de vista

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; } 
    ... 

y por supuesto que tendría que cambiar su xaml ligeramente

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

Gracias @Phil. +1. Fue una llamada cercana para aceptar. – Uri

Cuestiones relacionadas