3

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!

Community
  • 1
  • 1
Turkwise
  • 137
  • 3
  • 17
  • in your current approach you might be consuming excessive memory. perhaps slowing down the app too. if it is about thousands images opt for `Virtualization`. – pushpraj Aug 01 '14 at 15:14
  • It would be slow if I were to load the thumbnail when I'm adding the item to the ObservableCollection. It's not slow when I use the placeholder method I'm currently using. I just want each image to display as it's loaded, but still allow the users to interact with the ListBox. Thank you for your response! – Turkwise Aug 01 '14 at 15:21
  • so where are you stuck? I mean what is the question here except looking for the direction? – pushpraj Aug 01 '14 at 15:30
  • I'm sorry! I guess I'm asking how can I update the ListBox ItemsSource with the collection without it freezing the UI? I'm replacing the placeholder image for each item in the collection with it's proper thumbnail. After I update the item in the collection, I want it to display the new thumbnail in the ListBox. I haven't been able to do that. :( – Turkwise Aug 01 '14 at 15:34
  • 1
    Here is a suggestion, store only the image path in the collection, bind to the listbox, and define a data template for the listbox and use the container to load image asynchronously while displaying a placeholder in the meanwhile. I am away from my pc, I may give it a try once I reach home, you may give it a shot in the meanwhile. – pushpraj Aug 01 '14 at 15:40
  • I do have the image path stored in the collection as 'tag', so that takes out one step haha. I'll attempt to look up what you said and get back to you. Thanks for the push! – Turkwise Aug 01 '14 at 15:50

2 Answers2

4

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.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • 1
    Okay, I got it now, but it's still just loading all the images at once, and then displaying them as a whole. I want it to display the images as they load. – Turkwise Aug 01 '14 at 19:41
  • You need to make sure that FilePaths property gets populated dynamically - create another thread and start adding file paths by a group of 5, then send them to main UI thread through Dispatcher and adding them to FilePaths observableCollection. Make sure you do a little sleeping between adds, 15ms or so. This way you can get the effect you want. – Erti-Chris Eelmaa Aug 01 '14 at 20:10
1

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.

Turkwise
  • 137
  • 3
  • 17