2

I'm trying to load a rather big list of items into an WPF DataGrid. Problem is: It's painfully slow. Right now I have about 20,000 items in my list and it takes forever (well ... in my latest version it takes about 10 seconds, but that's not nearly good enough). Im working on it for the last couple of days, but I don't find a solution that is really working.

1) Of course UI virtualization is enabled (that is not the problem anymore)

2) I also tried some solution as described by Bea Stollnitz here and others. These solutions are great, but do not work for me since my collection has to be updated, filtered and sorted at runtime without reloading the collection. The solutions I found are only working with IList implementations ... I need a Observable collection to keep my items up to date.

This is the situation: I do have a Observable collection of my domain data obejcts (it's updated via WCF asychrously). I do have a ViewModel for the list items wrapping the domain objects. When I open the view I have a second list that will get populated with an ViewModel instance for each domain object. An THAT is the real problem. Since the "target" collection is bound to my DataGrid, I have to dispatch the creation of the ViewModels into the UI thread (otherwise .Net is not very happy about the collection change from another thread). So I am polluting the Dispatcher queue with 20,000 view model creation calls. Creating a ViewModel is pretty cheap, but I still have 20,000 calls in the dispatcher queue and at the same time the DataGrid is demanding CPU in the same thread to fill itself.

My idea (not quite finished): Since I already have UI virtualization in place, I would like to create the ViewModels NOT when I open my view, but on the fly then I need them. When the user can see the first 20 entries initially I only need to create 20 ViewModels and not 20,000. And here is my problem: I have no idea how. That's where you come in :)

I would like to have something like this (not working that way .. just to show what I mean):

<DataGrid ItemsSource={Binding MyDomainOjectCollection}>
    <DataGrid.RowStyle>
        <Style>
            <Setter Property="DataContext" 
                    Value="{Binding DataContext, 
                            Converter={StaticResource  MyViewModelFactoryConverter}}">
            </Setter>
        </Style>
    </DataGrid.RowStyle>
</DataGrid>

It does not have to be a Converter, it can also be something else or be done in code behind. I don't care. All I need is: Bind directly to the DomainObject collection, create the corresponding ViewModels on the fly and use the just created ViewModel instead of the original object to fill a single row. Any ideas?

harri
  • 556
  • 5
  • 17

2 Answers2

1

Flooding your ObservableCollection and the UI getting updated for every item is the issue, I think.

You should use BindingList. You can disable events on it while you add a range of items, like this:

BindingList.RaiseListChangedEvents = false;

which will stop the UI from receiving any events. Then after the bulk add, you can enable it with:

BindingList.RaiseListChangedEvents = true;
BindingList.ResetBindings();
Vladimir
  • 3,599
  • 18
  • 18
  • Thanks for your answer. you are right ... having thousands of UI updates because of thousands of collection changed definetly is part of the problem. Thank you so much for pointin out the BindingList class ... I didn't even knew there was something like that. I will try if I get the job done with the Binding list. – harri Aug 17 '11 at 22:34
  • @Vladimir, doesnt Microsoft forbid you from using BindingLists for WPF? It supports ObservableCollection for WPF.... – WPF-it Aug 18 '11 at 08:43
  • You can use it with ItemsSource. – Vladimir Aug 18 '11 at 09:22
0

When I open the view I have a second list that will get populated with an ViewModel instance for each domain object.

Good, that should be your filtered ObservableCollection.

Since the "target" collection is bound to my DataGrid, I have to dispatch the creation of the ViewModels into the UI thread

NO YOU DON'T. The ViewModel is a non-UI element, it can be created on any thread.It is the addition of these items to the ObservableCollection that needs to be done on the UI thread. So you can create all the VM wrappers anywhere you like, i suggest you do it in batches and then populate the batch into the ObservableCollection as one operation. Now i know that the ObservableCollection is setup to only add a single item at a time, but that is easily remedied by adding an extension method to it, as illustrated in this previous SO answer. Note that you only have to invoke the extension method on the UI thread, once there you are fine to add the items one by one.

Personally i would fight like crazy to not retrieve all the data all the time, I presume you have already analyzed this requirement and cannot avoid it. But do you really need to do the filtering and sorting on the client? Can this not be achieved by sending data to the server which then replies with the appropriate data?

In any case, if you must get all the data, then i suggest you do it in batches (while 20K items is not a huge amount, it is still reasonable). This can be done quite easily, and using property notifications in your viewmodel you can keep your filtered collection up to date with what has been added to your source collection.

Community
  • 1
  • 1
slugster
  • 49,403
  • 14
  • 95
  • 145
  • You are absolutly right ... only the modification operations have to be done on th UI thread. – harri Aug 17 '11 at 22:36
  • @harri - then extension methods to add/remove multiple items at once may be your best option, so that you don't have to repeatedly dispatch actions on the UI thread. – slugster Aug 17 '11 at 22:41
  • And no ... I can not reduce the numer of objects that should be visible. I have not tried if, but from looking at the code I would say the foreach extension you mentioned in the link is NOT doing an bulk nsert. It's still for each and still will result in 20k Add operations / events. And even if it could be done ... if I recall correctly, the DataGrid implementation has (or had?) some kind of issue with Range operations ... but maybe thats not true. – harri Aug 17 '11 at 22:44
  • @harri - yes you are correct that using the extension method the items are still added one at a time, the ObservableCollection only has an Add(), not an AddRange(). The point of my answer was that using the extension method you can marshall **one** action onto the UI thread instead of many. Maybe you should try this in combination with the BindingList mentioned by Vladimir. – slugster Aug 17 '11 at 22:54
  • got it. You mean just to reduce the number of operations in the dispatcher queue! However, I will do exactly what you said ... try it in combination with the Binding List. Theoratically, when I am able to discard the events, I should even be able to prepare the initial list in teh background thread altogether and only use dispatching later on to maintain the list. Sound good. Will try :) – harri Aug 17 '11 at 23:19