Почему <Image Source = '...'> так медленно, и что я могу с этим поделать? - PullRequest
7 голосов
/ 25 февраля 2012

Рассматривая следующий пример файла XAML, который показывает первые 1000 человек Facebook, начиная с 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;
    }
}

На очень разумном рабочем столе и высокой скорости соединения программа работает очень медленно. Попытка прокрутки с помощью полосы прокрутки ... скажем, в середине, займет 30 секунд. Нажатие клавиш «Домой» и «Конец» займет значительное время.

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

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

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

РЕДАКТИРОВАТЬ (резюме):

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

Ответы [ 2 ]

6 голосов
/ 25 февраля 2012

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

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

3 голосов
/ 25 февраля 2012

В качестве альтернативы можно добавить собственное кэширование изображений, чтобы изображения загружались только один раз.

Используя мой пример, вы поместите это в свой конструктор

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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...