2012-02-25 1 views
7

Учитывая следующий образец файла XAML, в котором показаны первые 1000 человек Facebook, начиная с 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 секунд. Нажатие клавиш «Home» и «End» займет много времени.

Это не проблема, связанная с первым обращением к изображениям. Переход туда и обратно и просмотр уже представленных изображений происходит несколько быстрее, но в целом очень медленно. Похоже, что ничего не хранится в кеше, закрывая приложение и перезагружая его, все происходит медленно.

Эквивалентный HTML-код быстро блестит. Некоторая медлительность в первый раз, но потом все очень быстро.

Что происходит? Использует ли элемент какое-либо кеширование вообще? Отображает ли список предварительный выбор изображений, которые в настоящее время не представлены? Есть ли вообще сказать, что нужно делать? Действительно ли это единственное решение для управления объектами Bitmap, а также кэширование и предварительная выборка логики? Если да, то любая предыдущая работа, которую я могу включить?

EDIT (резюме):

  1. @ H.B. ответ для отключения виртуализации даст вам лучший результат. Весь список отображается, как только загружается окно, и никакое изображение не пересчитывается.
  2. Код @Phil отлично работает, и это улучшает производительность, особенно при движении назад и вперед.
  3. Без какого-либо дополнительного кода WPF не будет кэшировать изображения между вызовами. Кэш WinINET: НЕ используется. Хотя запрос поставляется с инструкцией Cache в заголовке HTTP, WPF ничего не делает с ним.

ответ

6

ListBoxes виртуализировать элементы по умолчанию, поэтому при прокрутке вниз элементы создаются «на лету». Сначала нужно загрузить изображение, затем оно его декодирует. Если вы прокрутили все изображения, они могут быть кэшированы, но ListBox все равно воссоздает элементы управления Image и, следовательно, изображения должны быть снова декодированы каждый раз.

Вы можете отключить виртуализацию, установив VirtualizingStackPanel.IsVirtualizing attached property в false на ListBox тогда все будет загружаться сразу, или вы можете изменить VirtualizationMode к Recycling, то Images (и содержащий ListBoxItems) не будут выброшены раз создано.

+0

Спасибо H.B. +1 и принять. – Uri

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

Спасибо @Phil. +1. Это был призыв к согласию. – Uri

Смежные вопросы