0

I am using a ListView in WPF to display some images, which is done by filling up an ObservableCollection with Image objects and setting that list as ItemsSource. My implementation is shown here:

    public partial class MainWindow : Window
{
    public ObservableCollection<Image> imageList;

    public MainWindow()
    {
        InitializeComponent();
        imageList = new ObservableCollection<Image>();
        ImageView.ItemsSource = imageList;
    }

    private void loadImages(object sender, RoutedEventArgs e)
    {
        List<string> filePaths = Directory.GetFiles(@"C:\SomeImages")
        foreach (string filePath in filePaths)
        {
            Image image = new Image();
            BitmapImage bimage = new BitmapImage();
            bimage.BeginInit();
            bimage.UriSource = new Uri(filePath, UriKind.Absolute);
            bimage.EndInit();
            image.Source = bimage;
            imageList.Add(image);
        }
    }
}

My intention was for the UI to update once every iteration of the foreach loop as the observable list is filling up. However the foreach loops fills up the list completely before returning control to UI, which then shows all the pictures at the same time. This lags out the program for several seconds as there are around 100 pictures.

Is there any way i can make the program update the UI one element at a time, and possibly also keep UI responsive?

Thomas B.
  • 85
  • 1
  • 1
  • 6

1 Answers1

1

Sounds like you need to load images in asnyc way

private async void loadImages(object sender, RoutedEventArgs e)
{
    await Task.Run(() =>
    {
        var filePaths = Directory.EnumerateFiles(@"C:\SomeImages");

        foreach (string filePath in filePaths)
        {
            var bimage = new BitmapImage();
            bimage.BeginInit();
            bimage.CacheOption = BitmapCacheOption.OnLoad;
            bimage.UriSource = new Uri(filePath, UriKind.Absolute);
            bimage.EndInit();
            bimage.Freeze();

            Dispatcher.Invoke(() => imageList.Add(new Image { Source = bimage }));
        }
    });
}

But you should not create UI elements in code behind. Move the Image element to the ItemTemplate of your ListBox:

<ListBox x:Name="ImageView">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Change the code behind to this:

private readonly ObservableCollection<ImageSource> imageList
    = new ObservableCollection<ImageSource>();

public MainWindow()
{
    InitializeComponent();
    ImageView.ItemsSource = imageList;
}

and add the BitmapImages directly to the list:

Dispatcher.Invoke(() => imageList.Add(bimage)); 
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • I think better way would be to do just `imageList.Add(image)` in `Dispatcher.Invoke`, the rest can be done async. It will work much smoother – Krzysztof Skowronek Jul 03 '19 at 06:49
  • 1
    @KrzysztofSkowronek You are right, all relevant code would still have run in the UI thread. I've completely rewritten the answer so that it makes sense. – Clemens Jul 03 '19 at 07:27
  • Yeah, now it seems right :) – Krzysztof Skowronek Jul 03 '19 at 07:29
  • @Clemens Thanks for your edit, I've learned more things – Mr. Squirrel.Downy Jul 03 '19 at 07:30
  • Thank you, this is a good way to do it. However, i could only get this to work if i forced ImageView to update by adding `ImageView.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, (NoArgDelegate) delegate { }); ` to the other invoke call and defining `private delegate void NoArgDelegate();` as a field. – Thomas B. Jul 03 '19 at 23:29