Turkwise
Turkwise

Reputation: 147

WPF - Loading many images into ObservableCollection One by One

In WPF, I'm trying to load many (thousands) of images into a ListBox WrapPanel.

I'm attempting to load the images similar to how a Windows Explorer window does.

So far I have my code which loads all the images' name, location (as tag), and a placeholder image to speed up load time:

Dim ofd As New Microsoft.Win32.OpenFileDialog()
ofd.Multiselect = True
ofd.Filter = "JPEG|*.jpg"

If ofd.ShowDialog() Then
   For Each f In ofd.FileNames
      Items.Add(New With {.img = New BitmapImage(New Uri("pack://application:,,,/Resources/PlaceholderPhoto.png")), .nam = Path.GetFileNameWithoutExtension(f), .tag = f})
   Next

  'The name of my ObservableCollection is Items'
  lstboxPhotos.ItemsSource = Items 
End If

That part is fine. What I'm trying to do after is load the images' thumbnails dynamically (one-by-one). I'm doing this so the user can interact with and see how many images are available while the thumbnails are loading. I've tried a couple different things - a background worker, dispatcher, and separate thread.

The code I'm using to load the images is below:

    'i came from me doing a for..next for each image in Items collection'
    Dim bmp As New BitmapImage()
    bmp.BeginInit()
    bmp.DecodePixelWidth = 90
    bmp.DecodePixelHeight = 60
    bmp.CacheOption = BitmapCacheOption.OnLoad
    bmp.UriSource = New Uri(Items.Item(i).tag, UriKind.Absolute)
    bmp.EndInit()

    Items.Item(i).img = bmp

I've search all over the internet. I'm honestly not sure what direction to take in order to do what I'm needing to.

Let me know if I need to clarify anything else. Thank you in advance! :)

Edit: Okay so referring to this article, using the 2nd answer I got the items to load one at a time. It loads all of the items' names just fine, but seems to stop loading the images after about 40 items.

If anyone could shed light on why it might cease loading the thumbnails, but continue loading the items' names that would be a great help! Thanks!

Upvotes: 3

Views: 1420

Answers (2)

Turkwise
Turkwise

Reputation: 147

Okay I know it's been a while, but I wanted to post what I ended up doing.

First, I loaded all the image names into the ListBox normally using an ObservableCollection with a temporary image:

Dim Items As New ObservableCollection(Of Object)
Dim Files() As String
...

For Each f In Files
    Items.Add(New With {.img = New BitmapImage(New Uri("/Resources/Photo.png")), .name = f})
Next

lbPhotos.ItemsSource = Items

Then I used a BackgroundWorker to replace each placeholder image with the actual image:

Private WithEvents bw As New BackgroundWorker
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles bw.DoWork
    ...

    For i = 0 To Items.Count - 1
        If bw.CancellationPending Then
            e.Cancel = True
        Else
            Dim n As String = Items(i).name
            Dim t As String = Items(i).tag

            Dim bmp As New BitmapImage
            bmp.BeginInit()                
            bmp.UriSource = New Uri(PathToImage, UriKind.Absolute)
            bmp.EndInit()
            bmp.Freeze()

            Dispatcher.BeginInvoke(Sub()
                                       Items.RemoveAt(i)
                                       Items.Insert(i, New With {.img = bmp, .name = n})
                                   End Sub)
        End If
    Next
End Sub

That allows for the user to interact with the UI while the images load.

Upvotes: 1

Sheridan
Sheridan

Reputation: 69989

You can do that quickly and easily using the built in TypeConverter that converts string file paths into ImageSource objects. Take this simple example that will show thumbnails of all of the images in your Pictures folder:

public ObservableCollection<string> FilePaths { get; set; }

...

FilePaths = new ObservableCollection<string>(Directory.GetFiles(
    Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)));

...

<ItemsControl ItemsSource="{Binding FilePaths}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding}" Width="100" Stretch="Uniform" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

In this example, each Image.Source property is data bound directly with one of the file paths in the FilePaths collection.

Upvotes: 4

Related Questions