Custom Sort Icons with Silverlight 2's DataGrid Control

While looking through the control templates for Silverlight 2's DataGrid, I noticed the DataGridColumnHeader defines a couple of StoryBoards that allow you to control how the headers are displayed as they pass through the three column states: {Sorted Ascending, Sorted Descending, Unsorted}.  And without too much work you can override the default display and customize it to your liking.  I took a crack at overriding these states to mimic the icons the awesome jquery tablesorter plugin uses - below is a sample table that shows how it turned out.  Read on for the details (there really aren't too many) and don't forget to check out the live demo and download.

Live Demo | Download

image

 

Default Sort Indicator

If you look at the default template for the DataGridColumnHeader you will see the control uses a Path shape (named SortIconElement) to describe the sorting icon (the value of the Data attribute is describing a basic triangle).  The initial value of the Path's Opacity property is set to zero, making it invisible.  So the bit of xaml below is telling the control to include a completely transparent triangle in the column header.  Which probably doesn't seem very useful, but because we can change this shapes attributes using the DataGridColumnHeader's StoryBoard's we can manipulate it so it displays the sorting indicator.    

   1: <Path Name="SortIconElement" Margin="3,0,3,0" Opacity="0" Grid.Column="1" Stretch="Uniform" Width="8" Data="F1 M -5.215,0.0L 5.215,0.0L 0,6.099L -5.215,0.0 Z ">
   2:   <Path.Fill>
   3:     <SolidColorBrush Color="#FF313131" />
   4:   </Path.Fill>
   5:   <Path.RenderTransform>
   6:     <ScaleTransform Name="SortIconTransform" CenterX="4" CenterY="2.5" ScaleX="1" ScaleY="1" />
   7:   </Path.RenderTransform>
   8: </Path>

       

Sorting State Transitions

As the column passes from the state to state (SortedAscending to SortedDescencing, SortAscending to Unsorted, etc...), we get the opportunity to let Silverlight's runtime know that we want to change some of the Path's attribute values.  Using this hook we can hide or show the triangle and reposition it - exactly what needs to be done to let the user know how the column values are sorted.  Below is a brief description of how the column headers default template handles these state transitions.

Unsorted State

When the header is in the unsorted state, we want to make sure the Path shape is completely transparent.  So the default template defines a StoryBoard that animates the Path's Opacity property from its current value to zero - causing the icon to disappear.

   1: <Storyboard x:Key="Unsorted State" >
   2:   <DoubleAnimation Storyboard.TargetName="SortIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.3" To="0.0" />
   3:   <DoubleAnimation Storyboard.TargetName="SortIconTransform" Storyboard.TargetProperty="ScaleY" BeginTime="00:00:0.3" Duration="00:00:0.0" To="1" />
   4: </Storyboard>

SortedAscending State

When the header enters the SortedAscending State we want to make the triangle visible and make sure it is pointing upwards.  To accomplish this the default template defines animates that move the values of the Path's Opacity property to 1 (making it visible) and positions the triangle so it is pointing upwards by animating the SortIconTransform's ScaleY property to a negative 1.

   1: <Storyboard x:Key="SortedAscending State" >
   2:   <DoubleAnimation Storyboard.TargetName="SortIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.3" To="1.0" />
   3:   <DoubleAnimation Storyboard.TargetName="SortIconTransform" Storyboard.TargetProperty="ScaleY" Duration="00:00:0.3" To="-1" />
   4: </Storyboard>

SortedDescending State

When the header enters the SortedDescending State we want to make we show the triangle pointing down.  To accomplish this the default template defines animates that move the values of the Path's Opacity property to 1 (making it visible) and flips it over positions the triangle so it is pointing down by animating the SortIconTransform's ScaleY property to a 1.

   1: <Storyboard x:Key="SortedDescending State" >
   2:   <DoubleAnimation Storyboard.TargetName="SortIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.3" To="1.0" />
   3:   <DoubleAnimation Storyboard.TargetName="SortIconTransform" Storyboard.TargetProperty="ScaleY" Duration="00:00:0.3" To="1" />
   4: </Storyboard>

 

Custom Sort Icon

My custom sorting icon also makes use of triangles, but I ...

  • Want 2 of them
  • Want them to always be visible
  • Want them to fade ever so slightly as the header makes the state transitions

So I include 2 Path shapes (SortAscIconElement and SortDescIconElement) in the template as follows.  When I define my state transitions I will include animations that modify the Opacity of these two shapes.

   1: <Path x:Name="SortAscIconElement" 
   2:       Fill="#FF313131" Stroke="#FF313131"
   3:       Grid.Row="1" Opacity="1" Stretch="Uniform" Width="8" 
   4:       StrokeThickness="0.5" StrokeLineJoin="Round" Data="F1 M -5.215,0.0L 5.215,0.0L 0,6.099L -5.215,0.0 Z " 
   5:       RenderTransformOrigin="0.5,0.5">
   6:     <Path.RenderTransform>
   7:         <TransformGroup>
   8:             <ScaleTransform CenterX="4" CenterY="2.5" ScaleX="1" ScaleY="1" />
   9:             <RotateTransform Angle="-180" />
  10:         </TransformGroup>
  11:     </Path.RenderTransform>
  12: </Path>
  13:  
  14: <Path x:Name="SortDescIconElement" 
  15:       Fill="#FF313131" Stroke="#FF313131"
  16:       Grid.Row="3" Opacity="1" Stretch="Uniform" Width="8" 
  17:       StrokeThickness="0.5" StrokeLineJoin="Round" Data="F1 M -5.215,0.0L 5.215,0.0L 0,6.099L -5.215,0.0 Z ">
  18:     <Path.RenderTransform>
  19:         <ScaleTransform CenterX="4" CenterY="2.5" ScaleX="1" ScaleY="1" />
  20:     </Path.RenderTransform>
  21: </Path>

 

Custom Sort State Transitions

Next, I redefined the sorting state transitions to use my new shapes.

Unsorted State

When the header is in the unsorted state, we want to make sure the both Path shapes are partially transparent.  So the default template defines a StoryBoard that animates the Opacity of both triangles to 0.2.  I also chose to have these animates run for a seventh of a second so the transitions between states appears to fade.

   1: <Storyboard x:Key="Unsorted State" >
   2:     <DoubleAnimation Storyboard.TargetName="SortAscIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.7" To="0.2" />
   3:     <DoubleAnimation Storyboard.TargetName="SortDescIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.7" To="0.2" />
   4: </Storyboard>

SortedAscending State

When the header enters the SortedAscending State we want to make the top triangle completely visible.  To accomplish this I move the the value of the Path's Opacity property to 1 (making it visible).

   1: <Storyboard x:Key="SortedAscending State" >
   2:     <DoubleAnimation Storyboard.TargetName="SortAscIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.7" To="1.0" />
   3: </Storyboard>

SortedDescending State

When the header enters the SortedDescending State we want to make the bottom triangle completely visible.  To accomplish this the template animates that the value of the Path's Opacity property to 1 (making it visible).

   1: <Storyboard x:Key="SortedDescending State" >
   2:     <DoubleAnimation Storyboard.TargetName="SortDescIconElement" Storyboard.TargetProperty="Opacity" Duration="00:00:0.7" To="1.0" />
   3: </Storyboard>

 

Conclusion

And finally I think it is worth pointing out that none of the customizations I made required making a code change to the DataGrid control - I didn't have to inherit from it or handle any special events in the page's codebehind.  Instead I just used the controls template to define what I want the sort icon to look like and what properties need to be changed when as the control passes through the pre-defined states. 

 

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/145

Comments


Wow. This is a great example. Can not wait to play with this. I wish there were more hours in the day.

Posted by: vishal on August 16, 2008 03:16 AM

Hi Matt,
Great Article.

Hoping for more silverlight articles!!!

Good one Matt.
I am regular reader and have used your ajax tab skins in many apps.

Also bookmarked here:
http://codebounce.com/ASPNET

Best
Tom

Posted by: Mike on October 27, 2008 02:39 PM

Have you tried this with the final version of SilverLight 2.0, or just the beta? I have the latest version of Silverlight installed and I still only see the "Install Silverlight" logo on all your Silverlight demos.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Mike wrote: Have you tried this with the final version of SilverLight 2.0, or just the beta? I have the latest v...
  • Tom George wrote: Good one Matt. I am regular reader and have used your ajax tab skins in many apps. Also bookmarke...
  • vishal wrote: Hi Matt, Great Article. Hoping for more silverlight articles!!!...
  • Robbie Huttenhower wrote: Wow. This is a great example. Can not wait to play with this. I wish there were more hours in the...