In Silverlight, how to populate a sorted DataGrid from a dynamically changing connection
-
04-07-2019 - |
Question
I have a data set whose elements are displayed as rows in a DataGrid. The sort order for the rows changes in response to external events.
My initial thought was to store the rows as an ObservableCollection and resort the collection after updates. However I ran into two problems: 1) the ObservableCollection does not have a Sort() method 2) if I try to sort the elements myself, I get an exception whenever I try to assign an element to a new position, for example in a swap function like
class MyCollection : ObservableCollection<T>
{
void swap( int i, int j )
{
T tmp = this[i];
this[i] = this[j]; // THROWS A NOT SUPPORTED EXCEPTION
this[j] = tmp;
}
}
So the question is ... how to populate a DataGrid whose row order needs to update dynamically?
I did finally get one answer working, I'll describe it below.
Solution
I got this to work by implementing INotifyCollectionChanged explicitly (instead of using ObservableCollection). Furthermore, I found that using the Update action resulted in the same "not supported" error, but that I could use the Add and Remove actions. So my swap function ends up looking like this:
class MyCollection<T> : List<T>, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void swap( int i, int j )
{
T a = this[i];
T b = this[j];
// swap my own internal data storage
this[i] = b;
this[j] = a;
// and also let my CollectionChanged listener know that I have done so.
if( CollectionChanged != null )
{
NotifyCollectionChangedEventArgs arg;
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, a, i );
CollectionChanged( this, arg );
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, b, i );
CollectionChanged( this, arg );
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, b, j );
CollectionChanged( this, arg );
arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, a, j );
CollectionChanged( this, arg );
}
}
}
The dynamic changes are fairly local, so fortunately using a slower handwritten sort in response to changes is working OK for me. In other words, when updates arrive, I invoke another member function (in the same collection) that looks something like this:
public void ProcessUpdates( List<T> updateList )
{
// use the contents of updateList to modify my internal store
// ...
// and now resort myself
sort();
}
private void sort()
{
// implement your favorite stable sorting algorithm here, calling
// swap() whenever you swap two elements.
// (this is an intentionally facetious sorting algorithm, because I
// don't want to get into the long and irrelevant details of my own
// data storage.)
while( i_am_not_sorted() )
{
int i = random_index();
int j = random_index();
if( out_of_order(i,j) )
{
// modify my internal data structure and
// also let my CollectionChanged listener know that I have done so
swap( i, j );
}
}
}
Don't forget that it's also necessary to fire an "Add" notification when adding elements to the collection! I sort the initial list and then add in sorted order, which lets me use a more efficient library sort when I first populate the data.