Images not appearing on WPF form when loading asynchronously

491 views Asked by At

I'm attempting to display (in a ListBox with a custom DataTemplate) a series of BitmapSource frames (thumbnails) extracted from a multi-page tiff image. When I process the tiff on the UI thread, and either directly add the images to a listbox's item collection or add them to a bound ObservableCollection, they show fine in the list. However, when trying to load each thumbnail asynchronously (either with BackgroundWorker or using asynchronous tasks), I see behavior I can't work out:

  • The first thumbnail loads as expected
  • The next, and all subsequent thumbnails get items in the list (I see the border), but all that shows is a blank image. It shows the correct number of items, but no images after the first.

I've played around with trying to freeze the thumbnails (no good), trying to send them back to the UI thread and adding them to the collection there via the worker's ReportProgress (no good), but I can't seem to get them to show.

Working on UI thread (where SyncImages is an ObservableCollection bound to my ListBox, and OnPropertyChanged handles the notification event):

private void LoadSynchronous()
{
    Stream imageStreamSource = new FileStream(ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
    var decoder = BitmapDecoder.Create(imageStreamSource, BitmapCreateOptions.PreservePixelFormat,
        BitmapCacheOption.Default);

    foreach (var frame in decoder.Frames)
    {
        //frame.Freeze();  //Tried this but no effect.
        SyncImages.Add(frame);
    }

    OnPropertyChanged("SyncImages");
}

Not working (this example adds the frames directly to the list, but I've also tried via binding with no difference in result):

private void LoadAsync(object sender, DoWorkEventArgs e)
{
    Stream imageStreamSource = new FileStream(ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
    var decoder = BitmapDecoder.Create(imageStreamSource, BitmapCreateOptions.PreservePixelFormat,
        BitmapCacheOption.Default);

    foreach (var frame in decoder.Frames)
    {
        // frame.Freeze();
        (sender as BackgroundWorker).ReportProgress(0, frame);
    }

    OnPropertyChanged("AsyncImages");
}

private void ReportAsyncProgress(object send, ProgressChangedEventArgs e)
    {
        var frame = (BitmapSource) e.UserState;
        LbAsynchronous.Items.Add(frame);
    }

Hoping someone can shed some light on what is going on here. I know the code works to extract the frames, so even in the async example they must be loaded, but it seems almost like the UI thread can't access the properties of the source that hold the image data to display them on the form (which is why I tried freezing).

Any thoughts would be appreciated!

Example image: https://i.stack.imgur.com/xsarz.png

1

There are 1 answers

0
Chris Schubert On BEST ANSWER

@Clemens answer from his comment on the original question provided the solution. Ensuring that the file stream was being closed responsibly and changing the BitmapCacheOption to OnLoad now shows each image in the asynchronous load.

The final code for the asynchronous load looks like:

private void LoadAsync(object sender, DoWorkEventArgs e)
    {
        BitmapDecoder decoder;

        using (Stream imageStreamSource = new FileStream(ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            decoder = BitmapDecoder.Create(imageStreamSource, BitmapCreateOptions.PreservePixelFormat,
                BitmapCacheOption.OnLoad);

        }

        foreach (var frame in decoder.Frames)
        {
            frame.Freeze();
            (sender as BackgroundWorker).ReportProgress(0, frame);
        }
    }

    private void UpdateAsync(object send, ProgressChangedEventArgs e)
    {
            SyncImages.Add((BitmapSource)e.UserState);
            OnPropertyChanged("SyncImages");

    }