Posted by: Terry Nederveld | November 16, 2009

Silverlight 3 DataGrid Sorting

A little disclaimer before we go any further. Incase this doesn’t work for you, I am using Silverlight 3, .NET 3.5, RIA Services (July 2009 Preview). Moving on.

I had a task that I was trying to accomplish that lead me around in circles. A client requested that they could sort a list of tasks. So I thought what a better opportunity to take a look at Silverlight. I know, I know it is a little overkill for a simple ASP.NET DataGrid. But I wanted to learn something new. So off I went. Full steam ahead until I remembered a few things.

  1. Of course the new requirement of being able to sort.
  2. The task are using AJAX every 2 seconds to show any status changes.

So the first one was easy, setup up the .NET RIA services and in code did the following:

ServiceContext _entities = new ServiceContext();
PagedCollectionView pager;

public MainPage()
{
    InitializeComponent();
    LoadGrid();
}

private void LoadGrid()
{
    _entities.Load<Status>(_entities.GetStatusQuery());
    _entities.Load<User>(_entities.GetUsersQuery());

    LoadOperation loadOp =
        _entities.Load<Event>(_entities.GetTaskQueueQuery());
    loadOp.Completed +=
         new EventHandler(LoadTasks_Completed);
}

private void LoadTasks_Completed(object sender, EventArgs e)
{
    pager = new PagedCollectionView(
           _entities.Tasks.ToList<Task>()
           );

    dgTasks.ItemsSource = pager;
}

 

The code above populated the grid just fine. Now how do I go about getting the data into the grid every 2 seconds like the current ASP.NET page does. After searching for different ways to do it, the way that came up the most was adding a Timer into the project. I added the following code below the “LoadGrid()” call in the MainPage() function.

System.Windows.Threading.DispatcherTimer t =
    new System.Windows.Threading.DispatcherTimer();
t.Tick += new EventHandler(TickEvent);t.Interval = new TimeSpan(0, 0, 2);
t.Start();

Now I needed to add the TickEvent function.

private void TickEvent(object sender, EventArgs e)
{
    LoadGrid();
}

After I inputted that code, I went and tested it and it worked great. Until, the dreaded until, I clicked one of the headers to sort the data. Rock on it worked! Oh wait, when it refreshed the data in the grid it lost the sorting. Crap!

Ok before I freaked out to much I started looking and looking and looking and couldn’t find anything. So I went to sleep and woke up the next morning (today) and started searching again. I found a ton of stuff with really long ways to do it but I wanted something that was quick and worked without having to recode any Collections or Lists. Then I happened upon the answer, well a forum post that lead me in the right direction.

It said to use a INotifyCollectionChanged to set the CollectionChanged event on my PagedCollectionView. So I added the following code to my LoadTask_Completed event.

INotifyCollectionChanged sortchangeNotifier =
     pager.SortDescriptions as INotifyCollectionChanged;
sortchangeNotifier.CollectionChanged +=
     new NotifyCollectionChangedEventHandler(SortChanged);

Along with adding the code above I went and added the SortChaned event.

public void SortChanged(object sender,
     System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    // Do what you like here.
}

Now once you have this in place you use the EventArgs (e) to look at what is going on with the DataGrid. You will see an Action property that will be one of the following: Add, Remove, Replace and Reset. Since I need to preserve the sorting done by the end user I decided that I needed to store that information and then added it back into the DataGrid whenever it was refreshed. So I declared a new variable just under the PagedCollectionView.

SortDescriptionCollection sortOrder;

Then I added the following code to the SortChanged event.

if (e.Action == NotifyCollectionChangedAction.Add)
{
    SortDescriptionCollection sort = new SortDescriptionCollection();
    for (int i = 0; i < e.NewItems.Count; i++)
    {
        sort.Add(((SortDescription)e.NewItems[i]));
    }
    sortOrder = sort;
}

The code above looks to make sure the action that is taking place is an Add to the sort collection. Then it loops over the .NewItems and adds them to the sortOrder collection. Now that we have the sorting stored in our variable we need to apply them to the PagedCollectionView.

At first I just tried assigning the sortOrder variable to the PagedCollectionView.SortDescriptions (pcv.SD) property but discovered it does not like that. So I had to add them via the pcv.SD.Add(SortDescription) method. So below is the code that I used. I have the code in my LoadTasks_Completed event.

if (sortOrder != null)
    foreach (SortDescription sd in sortOrder)
    {
        pager.SortDescriptions.Add(sd);
    }

So first I check to make sure that the sortOrder collection is not null. If it is then I do nothing to the PagedCollectionView.SortDescriptions. The sortOrder will be null on the initial load of the application.

One thing that you can’t do is you can not add the code above after you have applied the INotifyCollectionChanged. Trust me. My final code ended up looking like the following:

using System;
using System.Collections.Generic;
using System.Linq;using System.Net;
using System.Windows;using System.Windows.Controls;
using System.Windows.Documents;using System.Windows.Input;
using System.Windows.Data;using System.Windows.Media;
using System.Windows.Media.Animation;using System.Windows.Shapes;
using Cliffs.Events.WebInterface;using Cliffs.Events.WebInterface.Models;
using System.Windows.Ria.Data;using System.Collections.ObjectModel;
using System.Collections.Specialized;using System.ComponentModel;

namespace Tasks.Silverlight
{
    public partial class MainPage : UserControl
    {
        ServiceContext _entities = new ServiceContext();
        PagedCollectionView pager;
        SortDescriptionCollection sortOrder;

        public MainPage()
        {
            InitializeComponent();
            LoadGrid();

            System.Windows.Threading.DispatcherTimer t = new System.Windows.Threading.DispatcherTimer();
            t.Tick += new EventHandler(TickEvent);
            t.Interval = new TimeSpan(0, 0, 5);
            t.Start();
        }

        private void LoadGrid()
        {
            _entities.Tasks.Clear();

            _entities.Load<Status>(_entities.GetStatusQuery());
            _entities.Load<User>(_entities.GetUsersQuery());

            LoadOperation loadOp = _entities.Load<Task>(_entities.GetTaskQueueQuery());
            loadOp.Completed += new EventHandler(LoadTasks_Completed);
        }

        private void TickEvent(object sender, EventArgs e)
        {
            LoadGrid();
        }

        private void LoadTasks_Completed(object sender, EventArgs e)
        {
            pager = new PagedCollectionView(
                   _entities.Tasks.ToList<Task>()
                   );

            if (sortOrder != null)
                foreach (SortDescription sd in sortOrder)
                {
                    pager.SortDescriptions.Add(sd);
                }

            INotifyCollectionChanged sortchangeNotifier = pager.SortDescriptions as INotifyCollectionChanged;
            sortchangeNotifier.CollectionChanged += new NotifyCollectionChangedEventHandler(SortChanged);

            dgTasks.ItemsSource = pager;
        }

        public void SortChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                SortDescriptionCollection sort = new SortDescriptionCollection();
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    sort.Add(((SortDescription)e.NewItems[i]));
                }
                sortOrder = sort;
            }
        }
    }
}

 

Thanks, I know that this will come in handy for myself if I forget how to do this. But I truly hope that it will help you.

Advertisements

Responses

  1. Bless you, sir. I am just starting with Silverlight, and this problem was driving me insane. Your solution was very educational.

  2. You use paged collection on client but first get all items from server. What happens when you got one milion or more records from database. Has your solution with paged collection resolve this problem? Where are you store retrived data. What about client memory usage?

  3. This is simply superb,but with more data it might not be workable….


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: