10

Here's what I have - a ListBox with an ItemsSource set to a ObservableCollection<T> - where T is my custom class representing a file, containing just 2 DependencyProperties: Filename and ThumbnailPath. - The listbox also has a custom DataTemplate defined, in order to nicely display a image and filename under it.

The purpose of the listbox is to display video files in the current folder (selected in a TreeView), with thumbnails (generated asynchronously; not part of this problem).

So when I change the folder in the TreeView, the ObservableCollection is cleared and filled up again, which is automatically reflected in the the ListBox items.

Here's the problem: The UI becomes unresponsive and it takes up to several seconds to update. Again, thumbnails do not have significance here (I tried disabling them). I think what takes the most time is the construction of 50-100 instances of my custom class, and their visual representation - it has to initialize an Image object for each one. But it's just my guess - could you please confirm or exclude the possibility?

I'm beginning to think ObservableCollection may not the way to go here, since from what I read and a little from what I tried, there's no way to add items asynchronously, at least if these items are DependencyObjects. I tried creating my class instances with a BackgroundWorker and adding them to the collection in the ProgressChanged event handler, but it throws an exception (some threading vs dependencyobjects problem).

Is there something that I'm missing? Or would I be better off by simply ditching the ObservableCollection and writing a good old async for loop to add the items?

oli.G
  • 1,300
  • 2
  • 18
  • 24

3 Answers3

20

Since your ObservableCollection is bound to UI hence it gets generated on UI thread so any further updates (delete/add/clear) have to be on the same UI thread. It doesn't allow updates from another thread.

However, what you can do is to create an instance of your class (or all time consuming operation on background thread) and once you are done, add the object in ObservableCollection using Dispatcher of your UI thread like this -

App.Current.Dispatcher.BeginInvoke((Action)delegate()
                          {
                              observableCollection.Add(instanceOfYourClass);
                          });

What Dispatcher do is put the operation on its associated thread. Hence, the item will always be added on UI thread but can be created in background thread.

Here are few links which might get you going - Updating from BW and the other one is here

Alin Ciocan
  • 3,082
  • 4
  • 34
  • 47
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • Thanks, I will try this out, and I'll definitely check out the interesting article, however I tried something similar (can't find it now) with no luck... I think it had a problem with my class being an DependencyObject. Something like "you can't create a dependencyobject on one thread and use it on another". – oli.G Oct 14 '12 at 10:56
  • Yeah your DP's have to be created on UI thread itself. In case you want to bind it to your UI, you have to use DP's but in case not, i would suggest to go for simple `POCO` properties implementing `INPC` interface. – Rohit Vats Oct 14 '12 at 11:04
  • I do want to bind it... I need the listbox to display a list of my FILE class instances, with my custom datatemplate applied. I think I'll go with INPC. – oli.G Oct 14 '12 at 11:25
11

With .net 4.5 you can use EnableCollectionSynchronization

 object lockObj = new object();
        BindingOperations.EnableCollectionSynchronization(yourObservableCollection, lockObj);
Andreas
  • 3,843
  • 3
  • 40
  • 53
2

yea its an old question, i know, but questions like this are ALL over. i found this possible before IAsynEnumerable existed but its even easier now that IAsyncEnumerable exists.

property in ViewModel class

public ObservableCollection<Card> Cards { get; set; }

ViewModel constructor

public CardViewModel(IDbContextFactory<CardContext> ccf)
{
    this.ctxFactory = ccf; //DB Context, using DI
    this.Cards = new ObservableCollection<Card>();

    Task.Run(async () =>
    {
        await foreach (Card card in GetAllCards())
        {
            this.Cards.Add(card);
        }
    });
}

private IAsyncEnumerable<Card> GetAllCards()
{
    CardContext cc = this.ctxFactory.CreateDbContext();
            
    return cc.Cards
        .Include(cc => cc.Edition)
        .Include(cc => cc.Site)
        .Include(cc => cc.Condition)
        .Include(cc => cc.Purchases)
        .Include(cc => cc.Trades)
        .AsNoTracking()
        .AsAsyncEnumerable();
}

Cards is bound in View to a Datagrid

ItemsSource="{Binding Cards}"

'Cards' are added to grid one at a time and i can interact with the grid while its loading and the app doesnt freeze.

data comes from DB using EF Core 5, which is what CardContext is, in this example

Aaron. S
  • 467
  • 4
  • 12