views:

580

answers:

4

Hi !

I'm trying to make a listbox that display pictures from internet. The items are provided by binding itemsource to a model that contain the URL of the image and some other properties (title, desc, etc...).

Unfortunately, the list is very slow to load because WPF is trying to download all pictures from the web before showing the list and it makes the application freeze for 15 to 25 sec.

I've read that I should load the picture in an other thread but I don't know where I should do it and how ? Is it better to load all pictures directly in the model (by creating a thread pool only for that - but the problem is that it's not really part of the model/modelview) or is that better to create a background thread that will update directly the list when it has data ?

Thanks !

A: 

A very simple approach would be to use a System.ComponentModel.BackgroundWorker (more info) in the view model. Here's a trivial example:

using (BackgroundWorker bg = new BackgroundWorker())
{
    bg.DoWork += (sender, args) => FetchImages(viewModelObjectsNeedingImages);
    bg.RunWorkerAsync();
}

The BackgroundWorker also makes it very convenient to cancel the background task.

You might also want to look at UI virtualization.

IV
Just make sure that SynchronizationContext.Current is meaningful at time of BackgroundWorker starting. (Should be the WPF context, but just something to keep in mind).
pst
A: 

You can use this asynchronous observable collection to be able to bind your data source to your ListBox and still be able to load your data in another thread.

For an example on how to write such a thread, take a look at the BackgroundWorker documentation.

Also, you might want to consider lazy loading of your images, that is, only load the ones that are visible and a couple more at any time. This way, you gain two benefits: don't have to block the UI while fetching the images in your thread, and you can reuse your collection to only hold a few images at a time, preventing filling up the memory with lots of images at once if you plan on displaying, say, a couple of thousand. Take a look here for details on how such virtualization could be implemented.

luvieere
+1  A: 

The easy way is to just just set the Binding.IsAsync property like this:

<Image ImageSource="{Binding propertyThatComputesImageSource, IsAsync=true}" />

Each access to propertyThatComputesImageSource will be done from a ThreadPool thread. If the thread creates the image with ImageCacheOptions.OnLoad, it will block until the image is loaded. So the UI will start up immediately and images will be loaded in the background and appear when they are available.

Binding.IsAsync is a good solution for ten or twenty images, but is probably not a good solution if you have hundreds of images and the load delay is long, since you could end up with hundreds of threads. In that case, load the images outside of databinding entirely by using ThreadPool directly:

ThreadPool.QueueUserWorkItem((state) =>
{
  foreach(var model in _models.ToArray())
    model.ImageSource = LoadOneImage(model.ImageUrl);
});

This may need to be extended with a Dispatcher.Invoke or two if the model's properties are DependencyProperty, since they can't be accessed from a separate thread.

This technique can be extended to spawn a fixed number of workers to load images and break the work up between them so multiple image downloads are happening, but the number of simultaneous downloads is limited so you don't end up with hundreds of threads.

Ray Burns
A: 

Thanks to all of you !

All solutions should work :) In my case using IsAsync on the image of the ListBoxItem is good enough (at most there are 50 items). Actually, it's not retrieving the image from a network which was taking too much time !

Unfortunately my problem was somewhere else... It's related to a bug with proxy detection in .NET 3.5 which cause the application to load very slowly :

If there isn't any your_application_name.exe.config file in the application folder with the following code - .NET can take a lot of time to detect the proxy which freeze the application the first time that it accesses to a network :

<configuration>   
  <system.net>
    <defaultProxy enabled="false"/>   
  </system.net> 
</configuration>
Ariel32