Conditional Formatting in the Silverlight DataGrid
I’ve been an asp.net developer for some time now and I was excited to jump on to Silverlight when it 2.0
was released a few months ago. One thing I really struggled with was applying conditional formatting to
the individual cells in the DataGrid control. Coming from an asp.net background, I carried a lot of
assumptions with me (big mistake). I thought I could get a hold of the rows or cells collection and have
my way with it, but no such luck. I stumbled down several paths which all ultimately lead to dead ends.
After killing hours (maybe days) on trying to figure this out, I had to let it go and move on. Now a month
later I decided to give it another shot and I finally got it. The answer was right in front of me all along.
I knew about the IValueConverter interface, but I didn’t fully understand its capabilities. I thought it was
only used for converting an object into a text representation or vice versa. Actually, you can return
anything you want from it. So you can return a Button, Grid, or whatever.
Another aspect I couldn’t figure out was how to get access to page members from within the Convert
method. For example, I wanted to render a button in the cell and wire up the button’s click event to a
method in the page. Sure, you could do this to a certain extent using templates, but then I couldn’t
find a way to change the template conditionally based on a value in the bound data item.
The solution I came up with was to create a delegate along with a class which implemented the
IValueConverter interface and exposed two events. One for converting and the other for converting
back. I can then declare this converter in the resources collection and setup a handler in the page
as shown below.
<UserControl.Resources>
<local:UniversalConverter x:Key="ageConverter" Converting="ConvertAge" />
<local:UniversalConverter x:Key="nameConverter" Converting="ConvertName" />
</UserControl.Resources>
Here is the markup i used in the “First Name” column of the DataGrid.
<data:DataGridTemplateColumn Header="First Name">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding Converter={StaticResource nameConverter}}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
Here is the “ConvertName” method I wired up from the converter defined in the resources collection.
1: private object ConvertName(object value, Type targetType,
object parameter, CultureInfo culture) {
2: Employee employee = value as Employee;
3: if (employee == null) {
4: return value;
5: }
6:
7: if (employee.FirstName.Contains('a')) {
8: Button btn = new Button();
9: btn.Content = employee.FirstName;
10: btn.Click += ((sender, e) => {
11: HtmlPage.Window.Alert(
12: string.Format(
"There is a button here because \"{0}\" contains an \"a\".",
13: employee.FirstName));
14: });
15:
16: return btn;
17: }
18: return new TextBlock {Text = employee.FirstName};
19: }
This is the IConverter class and delegate I created to handle the conversions.
1: public class UniversalConverter : IValueConverter {
2:
3: public object Convert(object value, Type targetType,
object parameter, CultureInfo culture) {
4: return this.OnConverting(value, targetType,
parameter, culture);
5: }
6:
7: public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) {
8: return this.OnConvertingBack(value, targetType,
parameter, culture);
9: }
10:
11: protected object OnConverting(object value, Type targetType,
object parameter, CultureInfo culture) {
12: UniversalConverterHandler handler = this.Converting;
13: if (handler != null) {
14: return handler(value, targetType, parameter, culture);
15: }
16: return value;
17: }
18:
19: protected object OnConvertingBack(object value, Type targetType,
object parameter, CultureInfo culture) {
20: UniversalConverterHandler handler = this.ConvertingBack;
21: if (handler != null) {
22: return handler(value, targetType, parameter, culture);
23: }
24: return value;
25: }
26:
27: public event UniversalConverterHandler Converting;
28:
29: public event UniversalConverterHandler ConvertingBack;
30:
31: }
32:
33: public delegate object UniversalConverterHandler(object value, Type targetType,
object parameter, CultureInfo culture);
And finally, here is a screenshot of the DataGrid. (Ugly, I know, but it proves my point.)
Here is a link to the complete solution.
I hope you find this useful and I look forward to hearing feedback and suggestions.
Most importantly, let me know if you know of a better way to do this.
Thanks
-Joe