WPF - 将许多图像逐个加载到ObservableCollection中

3
在WPF中,我正在尝试将许多(数千个)图像加载到ListBox WrapPanel中。
我试图以类似Windows资源管理器窗口的方式加载这些图像。
到目前为止,我已经编写了代码,加载所有图像的名称、位置(作为标记)和一个占位符图像以加快加载时间。
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

那部分没问题。接下来我想做的是动态地(逐个)加载图像缩略图。我这样做是为了让用户在缩略图加载时可以与之交互并查看可用的图像数量。我尝试了几种不同的方法-后台工作器、调度程序和单独的线程。
我使用的加载图像的代码如下:
    '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

我在互联网上搜寻了很久,但实际上不确定应该采取什么方向来完成我需要的内容。
如果需要我澄清其他事项,请告诉我。先谢谢你了! :)
编辑:好吧,参考this article,使用第二个答案,我让项目逐个加载。它可以很好地加载所有项目的名称,但似乎在大约40个项目后停止加载图像。
如果有人能够解释为什么会停止加载缩略图,但继续加载项目名称,那将是一个很大的帮助!谢谢!

在你目前的方法中,你可能会消耗过多的内存,这可能会导致应用程序变慢。如果涉及到数千张图片,请选择“虚拟化”。 - pushpraj
如果我在将项目添加到ObservableCollection时加载缩略图,那么速度会很慢。但是,使用我目前正在使用的占位符方法时,速度不会很慢。我只想让每个图像在加载时显示,但仍然允许用户与ListBox交互。谢谢您的回复! - Turkwise
那你卡在哪里了?我的意思是除了寻找方向之外,你还有什么问题吗? - pushpraj
非常抱歉!我想问一下,如何在不冻结用户界面的情况下更新ListBox的ItemsSource集合?我正在用其相应的缩略图替换集合中每个项的占位符图像。在我更新集合中的项之后,我希望在ListBox中显示新的缩略图像。但是我一直无法实现这一点。:( - Turkwise
1
这里有一个建议,只在集合中存储图像路径,绑定到列表框,并为列表框定义数据模板,使用容器异步加载图像,同时显示占位符。我离开了我的电脑,一旦回家我可以尝试一下,你可以在此期间尝试一下。 - pushpraj
我已经将图像路径存储在集合中作为“标签”,这样就省去了一步哈哈。我会尝试查找你说的内容并回复你。谢谢你的推动! - Turkwise
2个回答

4
您可以使用内置的快速轻松地将文件路径转换为对象。以下是简单示例,将显示您文件夹中所有图像的缩略图:
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>

在这个例子中,每个 Image.Source 属性直接与 FilePaths 集合中的一个文件路径进行数据绑定。

1
好的,我现在明白了,但它仍然只是一次性加载所有图像,然后作为整体显示它们。我希望它在加载时逐个显示图像。 - Turkwise
你需要确保FilePaths属性动态填充 - 创建另一个线程并开始以5个为一组添加文件路径,然后通过Dispatcher将它们发送到主UI线程,并将它们添加到FilePaths observableCollection中。确保在添加之间做一些睡眠,大约15毫秒左右。这样你就可以得到你想要的效果。 - Erti-Chris Eelmaa

1

好的,我知道已经有一段时间了,但是我想发布我最终做的事情。

首先,我使用临时图像将所有图像名称正常加载到ListBox中,使用ObservableCollection

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

然后我使用了一个BackgroundWorker来替换每个占位图像为实际图像:

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

这使用户能够在图片加载时与UI进行交互。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接