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.)
image

 

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

14 Comments

  • Timely and significantly helpful...thanks!

  • Very useful implementation of the cumbersome IValueConverter.

  • Your ConvertAge overrides the grid selection and mousemove backcolor behavior.
    Do you have a way to use both conditional formatting and grid selection behavior?

  • This conditional formatting blog was a lifesaver as I needed to conditionally turn an ID column into a button. I'm having trouble with it though.

    Let's say I click on a button in row 2, the button handling works fine as you've outlined. But then when I page the grid, row 2 is invisible. I can repopulate the grid, and still row 2 is invisible. I have to reload the SL app for the grid to render row 2 again. Then it pages correctly until I click another button.

    I'm manually handling paging by only displaying "1 page of data" at a time. Paging works fine until I click a button in the datagrid. Any thoughts?

  • Hi,
    while your example application works at my computer, I tried to implement the UniversalConverter in my own project.

    In XAML I defined:


    But when I try to compile, I get the Error No overload for 'RaWIConvertDate' matches delgate 'UniversalConverterHandler

    The same error occurs if I try to add the ConvertName and ConvertAge methods.
    If I use the "standard" event handler signature
    object RaWiConvertDate(object sender, EventArgs e) it compiles fine, but I get an exception in InitializeComponent() then.

    Has anybody an idea what I'am doing wrong. Its frustrading to have a running sample and don't get it to work in my projekt :-(

    Thanks
    Ralf

  • Great idea Joe.
    I just wanted the stying capability and importantly had to use a descendant of datagridboundcolumn.
    So I have my own class that extends boundcolumn and then in generateElement I add:
    cell.SetBinding(DataGridCell.StyleProperty, sb);
    where sb is my binding that uses the universalconverter. I add my event handler at runtime based on the viewmodel associated with the column.
    Thanks very much for this inspiration.

  • Excellent - thanks.

  • I'm having the same problem as Ralf above. Using SL3. Covnerted your project to SL3, changed the data reference and it works fine. But when I implement in my project I get the same error as Ralf. I've torn this thing apart and cant seem to find the problem.

  • Any luck with the
    No overload for 'RaWIConvertDate' matches delgate 'UniversalConverterHandler
    error? I'm having the same issue in my SL3 project, but not the converted sample project.

  • I too am having the "No overload for '***' matches delgate ''***' error, and I would love it if someone could shed some light on it for us.

  • After some hair pulling, I got this to work. In essence, we just need to add the delegate handler programatically. Only two changes need to be made.

    First, remove the Converting="ConvertAge" from the resource declaration in the XAML.

    Second, from code, add:
    UniversalConverter uc = this.Resources["ageConverter"] as UniversalConverter;
    uc.Converting += new UniversalConverterHandler(ConvertAge);

  • Dear Joe Wrobel,

    Great Post! You saved lot of my time.
    Thanks lot!

    siva.

  • @Jason Gray, thanks for figuring out that the "no overload" error could be fixed by doing it in code instead of XAML, but one wonders, why would it work in code and not XAML? Joe, any thoughts? And, Joe, thanks for doing this, it really helped me.

    Regards,

    Dave

  • I am trying to do this in the code behind and hopefully i will figure this out soon...

Comments have been disabled for this content.