Considering the following sample XAML file, which shows the first 1000 people of Facebook, starting with markz as 4th person. Note that this is only a sample. Any Window with 1000 element, no matter how you construct it, is a good demonstration.
<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>
And the code behind:
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;
}
}
On a very reasonable desktop and high speed connection, the program is extremely slow. Trying to scroll with the ScrollBar ... say to the middle, will take 30 seconds. Hitting the 'Home' and 'End' keys will take significant amount of time.
This isn't a first-time-only-get-images-to-the-cache issue. Going back and forth and seeing pictures already presented is somewhat faster but generally very slow. It appears that nothing is stored in cache, closing the application and restarting it, everything is slow again.
The equivalent HTML code is bleezing fast. Some slowness first time, but then everything is very fast.
What is going on? Does element use any caching at all? Does the list do any pre-fetch of the images not currently presented? Is there anyway to tell it to do? Is it really that my only solution is to manage Bitmap objects myself, along with caching and prefetching logic? If so, any previous work I can incorporate?
EDIT (summary):
ListBoxes
virtualize the items by default, so if you scroll down the items are created on the fly. At first it needs to download the image, then it decodes it. If you have scrolled through all the images they may be cached but the ListBox
will still recreate the Image
controls and hence the images need to be decoded again every time.
You could turn off virtualization by setting the VirtualizingStackPanel.IsVirtualizing
attached property to false
on the ListBox
then everything will be loaded right away, or you can change the VirtualizationMode
to Recycling
, then the Images
(and containing ListBoxItems
) won't be thrown away once created.
An alternative would be to add your own image caching so that images are only download once.
Using my example you would put this in your constructor
this.DataContext = new ViewModel();
The following class would store the url and then download the image when the Image property was first accessed.
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;
}
}
}
Here's the view model
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; }
...
and of course you would need to change your xaml slightly
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With