Wpf DataVirtualization - c#

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?

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();

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.

Related

Pass WPF Datagrid.SelectedItems to BackgroundWorker.RunWorkerAsync()

I've bound a wpf DataGrid to an ObservableCollection of a class called pictures and I wont to work with the selected items in a BackgroundWorker.
Obviously, I could copy all the data to new ObservableCollection item by item.
Since I'm very new to C#, I wonder whether there is an easy/elegant/better way to pass the SelectedItems collection to the BackgroundWorker.RunWorkerAsync() method and work with them as picture objects?
Thanks!
Edit:
With the application I want to select files (mostly pictures) from a datagrid and upload these files to a sharepoint site. I works fine without the BackgroundWorder. However, the files can be big and the number of upload is up to a 100 files / pictures. Hence, I want to use a BackgroundWorker to update the UI every now and then.
Edit II:
The Datagrid is bound like this
Datagrid.Itemssource = vm.getfiles(folderpath); //where getfiles is ObservableCollection<picture>
When I call the RunWorkerAsync like this:
bgw.RunWorkerAsync(Datagrid.SelectedItems)
and try to access them in the DoWork like this
ObservableCollection<picture> si = (ObservableCollection<picture>)e.Argument;
I get an System.InvalidCastException: The Object of Type System.Windows.Controls.SelectedItemCollection can not changed to MyApp.ObservableCollection.
If you using the MVVM pattern I would take the model, if it doesn't implement INotifyPropertyChanged. Then with your model you can make sure that you safely make changes to the object. Once your BackgroundWorker complete's its task you can then raise the proper PropertyChanged events on each ViewModel that should wrap your model.This way the changes are then reflected in the view. The models would be passed in as an expected argument in its own list. The observable collection should not be referenced in the BackgroundThread (Unless you really need it to update the UI and even then you will need to send an action to the dispatcher).
A significant amount of this advice can be taken with a large grain of salt. However, this is the general way I would approach a problem with needing to do a large amount of work in a background thread with out worrying about the UI Thread update problems.

Turn the binding on and off

Suppose you have a ViewModel with an ObservableCollection (OC) that is bound to some control inside the View.
Is there a way to temporarily disable the binding between the OC and the Control without removing the binding ? I want to have the ability to modify my ObservableCollection without having the View being informed of it.
The reason for that is simple: I am doing a lot of Add() and Insert() operations on the OC. Most of the time everything is OK but sometimes I am calling Add() so frequently that the updates in the View are looking unpleasant. For these periods of time I want to 'switch off' the binding beforehand and 'turn it on' afterwards.
Has anybody been in a similar situation / does anybody has a tip ?
Just wrap up ObservableCollection (even inheriting from it) and then override mthod OnCollectionChanged by adding custom logic which will postpone event triggering considering your requirements, I believe this is a pretty standard way to implement own defferable observable collection.
When I have a lot of items in the collection, I prefer to use a List<> as the source of a CollectionViewSource. Then the view binds to the CollectionViewSource.View. CollectionViewSource has methods DeferRefresh() and Refresh() that let you do all the background work of changing the source list without view notifications. The performance of CollectionViewSource is way better than an ObservableCollection, plus it supports filtering. Filtering might be what you need if you are constantly adding and removing items.

Correctly setting visibilities of entries in a DataGridView from a BackGroundWorker

I have a DataGridView, which has some DataSource. I need to filter the items in the DataGridView, based on some criteria the user specifies.
The problem is that the data is on a very slow database. For many of the items I can filter on information which is stored on the items themselves (which is quick), but sometimes I need to query the database to figure out wether or not a row should be visible.
Hence, I use a BackGroundWorker in the following way:
I make a copy (using CopyTo) of the rows in the DataGridView and pass this to the RunWorkerAsync method
In the DoWork method, I check each of the DataGridViewRow elements in the array. Sometimes I can simply look up the information in the row(E.g. read column 2 on the DataGridViewRow), sometimes I need to query the database.
Each time i know wether or not a row should be visible, I send a tuple consisting of a row number(Simply the position in the passed array of DataGridViewRow elements) and a boolean indicating the visibility to the ReportProgress method, which sets the visibility on the DataGridView.
The reason for copying (using CopyTo) is to avoid accessing the DataGridViews RowCollection from another thread.
All this works fine, but I am really wondering if this is a really bad way to do it.
Is it a really bad practice to operate on a RowCollection like this?
If it is okay, is it necessary to use CopyTo? The reason I do it is because lists are passed by reference, and I wanted to avoid accessing the UI from a seperate thread.
Thanks for your time.
I think your GUI is too tight to the database for a background worker.
In general I prefer to use BindingList<T> to bind the grid to, and manipulate the list instead of going through database. But if I have a fairly big amount of data to retrieve from database then I might enable the "virtual mode" of the datagridview, and again make use of the BindingList<T>.
Hope this helps.
EDIT1: Of course you can use filtering directly over your DataTables... but then maybe you should not involve the background worker.
EDIT2: I see BindingList<T> as a chance to bind the view to a model, and not to the data layer directly, which in general is better because separates data layer of presentation layer. Even more, you could create a view-model from the model to bind the grid to (MVVM pattern). But really depends on your project.

INotifyCollectionChanged -- How often does it fire (and how do they make it so efficient/fast)?

Basically, I'm wondering how it is actually efficient here.
Sample code:
void GetItems()
{
foreach (var item in items)
myObservableCollection.Add(item);
}
Won't this fire off the CollectionChanged event every time causing the UI to have to refresh everytime? Or does it do it so that it waits til the GetItems function is done?
Basically, it seems that WPF handles it very well, and I'm wondering how they did that.
Optimizing Performance: Data Binding provides some background on how data bindings are resolved, including the performance implications of different items sources. Take a look at the Binding to an ItemsSource section.
Consider a scenario in which you have a CLR List object that holds
a list of employees that you want to display in a ListBox. To create a
correspondence between these two objects, you would bind your employee
list to the ItemsSource property of the ListBox. However, suppose you
have a new employee joining your group. You might think that in order
to insert this new person into your bound ListBox values, you would
simply add this person to your employee list and expect this change to
be recognized by the data binding engine automatically.
That assumption would prove false; in actuality, the change will not
be reflected in the ListBox automatically. This is because the CLR
List object does not automatically raise a collection changed
event. In order to get the ListBox to pick up the changes, you would
have to recreate your list of employees and re-attach it to the
ItemsSource property of the ListBox. While this solution works, it
introduces a huge performance impact. Each time you reassign the
ItemsSource of ListBox to a new object, the ListBox first throws away
its previous items and regenerates its entire list. The performance
impact is magnified if your ListBox maps to a complex DataTemplate.
A very efficient solution to this problem is to make your employee
list an ObservableCollection. An ObservableCollection object
raises a change notification which the data binding engine can
receive. The event adds or removes an item from an ItemsControl
without the need to regenerate the entire list.
Update time for 1 item (ms)
To a CLR List object = 1656 ms
To an ObservableCollection = 20 ms
WPF never binds directly to a collection. If you specify a collection as a binding source, WPF actually binds to the collection's default view.
A collection view is a layer on top of a binding source collection
that allows you to navigate and display the source collection based on
sort, filter, and group queries, without having to change the
underlying source collection itself. A collection view also maintains
a pointer to the current item in the collection. If the source
collection implements the INotifyCollectionChanged interface, the
changes raised by the CollectionChanged event are propagated to the
views.
The event will fire for every change.
The GUI does not have to react and refresh every time, it can postpone that.
I know WinForms will optimize this, I think WPF has a similar approach.
If you want to see how often the UI requests the fresh results expose it as a public property and put a debug line in the get (assessor) of the public property for myObservableCollection.

Best way to unbind wpf listview

I have WPF listview bound to collection of objects. Objects are continuously added in to collection from remote server and same is reflecting in to listview. Now we have requirement that we should be able to freeze listview for some time,
That is objects should still get added in to collection but should not appear in listview till we unfreeze it ( we have button to freeze and unfreeze) . What is the best way to do it, when listview is bound to collection ? How to unbind collection and rebind it ? and Will i still able to filter and sort when collection is unbound from listview ? Waiting for answer please reply
Regards
Sandeep
You can just break the binding. In your freeze button handler say:
listView = _list
Which will freeze it at that point. Then in your unfreeze handler set the binding back up:
listView.SetBinding(ListView.ItemsSourceProperty, New Binding("_list"))
I hope this helps.
You should just stop updating the collection when the list is frozen, if you must keep adding to the collection even when the list is frozen (if the same collection is used somewhere else that shouldn't be frozen) then consider having two collections, one always updated and the other one bound to the listview.
What is the list type you are using as the data-source? Can you use BindingList<T>? If so, this has a settable RaiseListChangedEvents property, allowing you to turn off notifications. This will probably get very messy re your last points, though (sort/filter).
Personally, I'd work with 2 lists - the bound one, and the one that the server is updating. When paused, simply stop pumping data between the two. This should leave the paused view free for sorting / filtering etc.

Categories