Adding additional power to RadGridView for Silverlight with attached behaviors

Saturday, October 03, 2009 by Pavel Pavlov | Comments 6

PART II [example: Conditional formatting of cells in RadGridView for WPF and Silverlight]

 

Lets say we have a list of products and we need to mark higher prices (>15.00)  with a red color. Something like :

 

prices_screenshot

 

First lets try it with RadGridView for WPF :

We have to create a small IValueConverter. It will take care to convert the value of the price to the appropriate color:

   1: public class PriceToColorConverter : IValueConverter
   2:     {
   3:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   4:         {
   5:             SolidColorBrush brush = new SolidColorBrush();
   6:             decimal price = (decimal) value;
   7:  
   8:             if (price > 15)
   9:                 brush.Color = Colors.Red;
  10:             else
  11:                 brush.Color = Colors.Green;
  12:  
  13:             return brush;
  14:         }
  15:  
  16:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  17:         {
  18:             throw new NotImplementedException();
  19:         }
  20:     }

 

We use this converter to bind the Foreground property of the cell to the product’s Price :

   1: <Grid>
   2:        <Grid.Resources>
   3:            <local:PriceToColorConverter x:Key="MyConverter" />
   4:        </Grid.Resources>
   5:        <telerik:RadGridView x:Name="RadGridView1" AutoGenerateColumns="False" >
   6:            <telerik:RadGridView.Columns>
   7:                <telerik:GridViewDataColumn DataMemberBinding="{Binding ProductName}" Header="Product" />
   8:                <telerik:GridViewDataColumn DataMemberBinding="{Binding Price}" Header="Price">
   9:                    <telerik:GridViewDataColumn.CellStyle>
  10:                        <Style TargetType="telerik:GridViewCell">
  11:                            <Setter Property="Foreground" Value="{Binding Price, Converter={StaticResource MyConverter}}" />
  12:                        </Style>
  13:                    </telerik:GridViewDataColumn.CellStyle>
  14:                </telerik:GridViewDataColumn>
  15:            </telerik:RadGridView.Columns>
  16:        </telerik:RadGridView>
  17:    </Grid>

And that is all – nice and easy! You may see this in action in 

 

Now lets  try it with RadGridView for Silverlight:

Lets play with the Background property this time.

You know – our RadGridView controls share a common codebase and in most cases code is reusable. Unfortunately not in this case. Silverlight does not support bindings in style setters.

Here is the workaround. We are going to reuse our nice little converter and we need to find it a place. A good place would be the template of the GridViewCell. We are going to modify it , binding the Background with the help of our converter. And here we have to paste the whole 100+  lines of XAML GridViewCell template.

   1: <UserControl x:Class="ConditionalCellFormatting.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   5:     xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.GridView"
   6:     xmlns:gridview="clr-namespace:Telerik.Windows.Controls.GridView;assembly=Telerik.Windows.Controls.GridView"
   7:     xmlns:local="clr-namespace:ConditionalCellFormatting"
   8:     mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
   9:     <UserControl.Resources>
  10:         <local:PriceToColorConverter x:Key="MyConverter" />
  11:         
  12:         <SolidColorBrush x:Key="GridLinesFill" Color="#FFB3B3B3"/>
  13:         <SolidColorBrush x:Key="GridViewRowNormalBackground" Color="#FFFFFFFF"/>
  14:         <LinearGradientBrush x:Key="GridViewRowSelectedBackground" EndPoint="0.5,1" StartPoint="0.5,0">
  15:             <GradientStop Color="#FFFFCA5D" Offset="1"/>
  16:             <GradientStop Color="#FFFFDC9C" Offset="0"/>
  17:         </LinearGradientBrush>
  18:         <SolidColorBrush x:Key="GridViewDisabledBackground" Color="#FFEEEEEE"/>
  19:         <SolidColorBrush x:Key="GridViewDisabledBorderBrush" Color="#FFBBBBBB"/>
  20:         <SolidColorBrush x:Key="GridViewDisabledForeground" Color="#FF6F6F6F"/>
  21:         <ControlTemplate x:Key="GridViewCellTemplate" TargetType="gridview:GridViewCell">
  22:             <Grid x:Name="SelectionBackground" Background="{Binding Price, Converter={StaticResource MyConverter}}">
  23:                 <VisualStateManager.VisualStateGroups>
  24:                     <VisualStateGroup x:Name="CommonStates">
  25:                         <VisualState x:Name="Normal"/>
  26:                     </VisualStateGroup>
  27:                     <VisualStateGroup x:Name="EditingStates">
  28:                         <VisualState x:Name="Edited">
  29:                             <Storyboard>
  30:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_EditorPresenter" Storyboard.TargetProperty="Visibility">
  31:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  32:                                 </ObjectAnimationUsingKeyFrames>
  33:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Visibility">
  34:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Collapsed"/>
  35:                                 </ObjectAnimationUsingKeyFrames>
  36:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="GridViewCellBorder" Storyboard.TargetProperty="Background">
  37:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource GridViewRowNormalBackground}"/>
  38:                                 </ObjectAnimationUsingKeyFrames>
  39:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CurrentBorder" Storyboard.TargetProperty="Visibility">
  40:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  41:                                 </ObjectAnimationUsingKeyFrames>
  42:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CurrentBorder" Storyboard.TargetProperty="Stroke">
  43:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource GridViewRowSelectedBackground}"/>
  44:                                 </ObjectAnimationUsingKeyFrames>
  45:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CurrentBorder" Storyboard.TargetProperty="StrokeThickness">
  46:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="2"/>
  47:                                 </ObjectAnimationUsingKeyFrames>
  48:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CurrentBorder" Storyboard.TargetProperty="StrokeDashArray">
  49:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="0"/>
  50:                                 </ObjectAnimationUsingKeyFrames>
  51:                             </Storyboard>
  52:                         </VisualState>
  53:                         <VisualState x:Name="Display">
  54:                             <Storyboard>
  55:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Visibility">
  56:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  57:                                 </ObjectAnimationUsingKeyFrames>
  58:                             </Storyboard>
  59:                         </VisualState>
  60:                         <VisualState x:Name="Current">
  61:                             <Storyboard>
  62:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_EditorPresenter" Storyboard.TargetProperty="Visibility">
  63:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Collapsed"/>
  64:                                 </ObjectAnimationUsingKeyFrames>
  65:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Visibility">
  66:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  67:                                 </ObjectAnimationUsingKeyFrames>
  68:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CurrentBorder" Storyboard.TargetProperty="Visibility">
  69:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  70:                                 </ObjectAnimationUsingKeyFrames>
  71:                             </Storyboard>
  72:                         </VisualState>
  73:                     </VisualStateGroup>
  74:                     <VisualStateGroup x:Name="ValueStates">
  75:                         <VisualState x:Name="Valid">
  76:                             <Storyboard>
  77:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility">
  78:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Collapsed"/>
  79:                                 </ObjectAnimationUsingKeyFrames>
  80:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ErrorIcon" Storyboard.TargetProperty="Visibility">
  81:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Collapsed"/>
  82:                                 </ObjectAnimationUsingKeyFrames>
  83:                             </Storyboard>
  84:                         </VisualState>
  85:                         <VisualState x:Name="Invalid">
  86:                             <Storyboard>
  87:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility">
  88:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  89:                                 </ObjectAnimationUsingKeyFrames>
  90:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ErrorIcon" Storyboard.TargetProperty="Visibility">
  91:                                     <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
  92:                                 </ObjectAnimationUsingKeyFrames>
  93:                             </Storyboard>
  94:                         </VisualState>
  95:                     </VisualStateGroup>
  96:                     <VisualStateGroup x:Name="DisabledStates">
  97:                         <VisualState x:Name="Enabled"/>
  98:                         <VisualState x:Name="Disabled">
  99:                             <Storyboard>
 100:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectionBackground" Storyboard.TargetProperty="Background">
 101:                                     <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource GridViewDisabledBackground}"/>
 102:                                 </ObjectAnimationUsingKeyFrames>
 103:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HorizontalGridLine" Storyboard.TargetProperty="Fill">
 104:                                     <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource GridViewDisabledBorderBrush}"/>
 105:                                 </ObjectAnimationUsingKeyFrames>
 106:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_VerticalGridLine" Storyboard.TargetProperty="Fill">
 107:                                     <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource GridViewDisabledBorderBrush}"/>
 108:                                 </ObjectAnimationUsingKeyFrames>
 109:                                 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Foreground">
 110:                                     <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource GridViewDisabledForeground}"/>
 111:                                 </ObjectAnimationUsingKeyFrames>
 112:                             </Storyboard>
 113:                         </VisualState>
 114:                     </VisualStateGroup>
 115:                 </VisualStateManager.VisualStateGroups>
 116:                 <Border x:Name="GridViewCellBorder" UseLayoutRounding="True" Background="Transparent" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"/>
 117:                 <gridview:AlignmentContentPresenter x:Name="PART_ContentPresenter" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Visibility="Visible" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Foreground="{TemplateBinding Foreground}" TextAlignment="{TemplateBinding TextAlignment}" TextDecorations="{TemplateBinding TextDecorations}" TextWrapping="{TemplateBinding TextWrapping}"/>
 118:                 <ContentPresenter x:Name="PART_EditorPresenter" HorizontalAlignment="Stretch" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Visibility="Collapsed"/>
 119:                 <Rectangle x:Name="PART_HorizontalGridLine" Fill="{StaticResource GridLinesFill}" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
 120:                 <Rectangle x:Name="PART_VerticalGridLine" Fill="{StaticResource GridLinesFill}" HorizontalAlignment="Right" VerticalAlignment="Stretch" Width="1"/>
 121:                 <Rectangle x:Name="CurrentBorder" Stroke="#FF000000" StrokeDashArray="1.5" StrokeDashCap="Round" StrokeDashOffset="0" StrokeThickness="1" Height="Auto" Margin="0" Width="Auto" Visibility="Collapsed"/>
 122:                 <Rectangle x:Name="InvalidBorder" Stroke="#FFDB000C" StrokeThickness="2" Height="Auto" Margin="0" Width="Auto" Visibility="Collapsed"/>
 123:                 <Grid x:Name="ErrorIcon" Height="18" HorizontalAlignment="Right" Margin="2,0,2,0" VerticalAlignment="Center" Width="18" Visibility="Collapsed">
 124:                     <Ellipse Fill="#FF535353"/>
 125:                     <Ellipse Fill="#FFCE3527" Stroke="#FFFFFFFF" Margin="1,1,1,1"/>
 126:                     <Path Stretch="Fill" Margin="0.999,1,1.001,8.134" Data="M16,8 C8.127,4.9999293 6.627,10.999763 0,8 0,3.581722 3.581722,0 8,0 12.418278,0 16,3.581722 16,8 z">
 127:                         <Path.Fill>
 128:                             <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
 129:                                 <GradientStop Color="#99FFFFFF" Offset="0"/>
 130:                                 <GradientStop Color="#33FFFFFF" Offset="1"/>
 131:                             </LinearGradientBrush>
 132:                         </Path.Fill>
 133:                     </Path>
 134:                     <Path Stretch="Fill" Stroke="#FF000000" StrokeThickness="2" Margin="5.168,5.748,4.832,4.252" Data="M0.50001547,0.5 L6.5000797,6.5000169 M6.5000155,0.5 L0.5,6.5000704"/>
 135:                     <Path Fill="#FFCE3527" Stretch="Fill" Stroke="#FFFFFFFF" StrokeThickness="2" Margin="5.085,5.084,4.915,4.916" Data="M0.50001547,0.5 L6.5000797,6.5000169 M6.5000155,0.5 L0.5,6.5000704"/>
 136:                 </Grid>
 137:             </Grid>
 138:         </ControlTemplate>
 139:         <Style x:Key="GridViewCellStyle" TargetType="gridview:GridViewCell">
 140:             <Setter Property="Template" Value="{StaticResource GridViewCellTemplate}"/>
 141:             <Setter Property="Padding" Value="3,0,3,0"/>
 142:             <Setter Property="BorderBrush" Value="{StaticResource GridViewRowSelectedBackground}"/>
 143:             <Setter Property="VerticalContentAlignment" Value="Center"/>
 144:             <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
 145:             <Setter Property="Background" Value="Transparent"/>
 146:             <Setter Property="UseLayoutRounding" Value="True"/>
 147:         </Style>
 148:     </UserControl.Resources>
 149:   <Grid x:Name="LayoutRoot">
 150:         <telerik:RadGridView x:Name="RadGridView1" AutoGenerateColumns="False" >
 151:             <telerik:RadGridView.Columns>
 152:                 <telerik:GridViewDataColumn DataMemberBinding="{Binding ProductName}" Header="Product" />
 153:                 <telerik:GridViewDataColumn  CellStyle="{StaticResource GridViewCellStyle}"  DataMemberBinding="{Binding Price}" Header="Price"/>
 154:             </telerik:RadGridView.Columns>
 155:         </telerik:RadGridView>
 156:     </Grid>
 157: </UserControl>

Here is what we have got this time:

 

prices_screenshot_Silverlight

 

For your copy/paste needs , please use

 

Now that is great. What I do not like here actually is that large ugly 5 screens of XAML thing. I may blame the Silverlight platform , I may blame our team for not providing a better way. I will not do that. Instead lets try find a better way via…

Using an attached behavior for conditional formatting in RadGridView for Silverlight

Here is the few-lines-of-code solution :

   1: <telerik:RadGridView x:Name="RadGridView1" AutoGenerateColumns="False" >
   2:             <i:Interaction.Behaviors>
   3:                 <local:ConditionalFormattingBehavior/>
   4:             </i:Interaction.Behaviors>
   5:             <telerik:RadGridView.Columns>
   6:                 <telerik:GridViewDataColumn DataMemberBinding="{Binding ProductName}" Header="Product" />
   7:                 <telerik:GridViewDataColumn DataMemberBinding="{Binding Price}" Header="Price"/>
   8:             </telerik:RadGridView.Columns>
   9:         </telerik:RadGridView>

 

The code of the formatting behavior itself (for brevity - hardcoded for the Price column) :

   1: public class ConditionalFormattingBehavior : Behavior<RadGridView>
   2:     {
   3:         protected override void OnAttached()
   4:         {
   5:             base.OnAttached();
   6:             this.AssociatedObject.RowLoaded += new EventHandler<Telerik.Windows.Controls.GridView.RowLoadedEventArgs>(AssociatedObject_RowLoaded);
   7:         }
   8:  
   9:         void AssociatedObject_RowLoaded(object sender, Telerik.Windows.Controls.GridView.RowLoadedEventArgs e)
  10:         {
  11:             if ((e.Row is GridViewHeaderRow) || (e.Row is GridViewFooterRow) || (e.Row is GridViewNewRow))
  12:                 return; //we want to apply logic to data rows only
  13:  
  14:             Binding colorBinding  = new Binding("Price"){Converter = new PriceToColorConverter()};
  15:             e.Row.Cells[1].SetBinding(GridViewCell.ForegroundProperty, colorBinding);
  16:             //e.Row.Cells[1].SetBinding(GridViewCell.BackgroundProperty, colorBinding);
  17:         }
  18:     }

And of course  a

   using the formatting behavior.

 

We often argue with our front-end developer about which way is better. For him some hundred lines of XAML is nothing to worry about and of course it is the natural Silverlight way of getting the job done. For me it is a silent panic. Now our mission is to give you the choice. You have both in your toolbox now. Drop me a line when you make the decision :) .

6 Comments

  • Mark Huck 08 Oct 2009
    Thanks, Pavel!  Implemented the Silverlight version today for a client ... works flawlessly.
  • Tom 22 Nov 2009
    I'm sure that given time I could learn the "real" silverlight way of doing this, but for me the "few-lines-of-code" silverlight way is a clear winner. Only thing I cant get to work is setting back colour in all circumstances, like when the row has .isEnabled=false. But using fore color is pretty close. Thanks.
  • eric 14 Mar 2010

    I'm converting from the toolkit grid to take advantage of the filters, performance, right click etc. etc. etc.

    this works and is consistent with the approach used with the toolkit



        <UserControl.Resources>
            <local:MAColorConverter  x:Key="MAColorConverter" />
        </UserControl.Resources>

                        <grid:GridViewDataColumn DataMemberBinding="{Binding ID}" Header="EMP ID">
                            <grid:GridViewDataColumn.CellTemplate>
                                <DataTemplate>
                                    <Border Background="{Binding ID, Converter={StaticResource MAColorConverter}}" Width="Auto">
                                        <TextBlock Text="{Binding ID}"/>
                                    </Border>
                                </DataTemplate>
                            </grid:GridViewDataColumn.CellTemplate>
                            <grid:GridViewDataColumn.CellEditTemplate>
                                <DataTemplate>
                                    <TextBox Text="{Binding ID, Mode=TwoWay}" />
                                </DataTemplate>
                            </grid:GridViewDataColumn.CellEditTemplate>
                        </grid:GridViewDataColumn>

    Public Class MAColorConverter
        Implements IValueConverter
        Public Function Convert(ByVal value As Object, _
                    ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
            Try
                Return New SolidColorBrush(Colors.Green)
            Catch
                MessageBox.Show("MAColorConverter")
                Return Nothing ' use the default
            End Try
        End Function

        Public Function ConvertBack(ByVal value As Object, _
                    ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Return Nothing ' use the default
        End Function
    End Class

  • Rodney 06 Dec 2010
    The core grid makes this a lot easier- you can just bind your converters to the grid background in the template and it fills the whole cell. In Telerik this does not work and only does the background of the textblock within the cell. This is a real pain...

    I don't like the solution of using cell indexes so you are relying on a certain number of cells...




                    <sdk:DataGridTemplateColumn>
                        <sdk:DataGridTemplateColumn.CellTemplate> 
                            <DataTemplate> 
                                <Grid 
                                    Background="{Binding StatusId, Converter={StaticResource WOStatusToColourConverter}}"
                                    <TextBlock 
                                        VerticalAlignment="Center" 
                                        Text="{Binding Status}" /> 
                                </Grid> 
                            </DataTemplate> 
                        </sdk:DataGridTemplateColumn.CellTemplate> 
                    </sdk:DataGridTemplateColumn> 
  • Rodney 03 Feb 2011
    Does anyone read these comments?

    After spending more time on this I have to say the Telerik solution is terrible compared to the core Silverlight DataGrid.

    In the core control, I just make a Converter based on a value and return a Brush. I can bind this to ANYTHING on ANY control that uses a Brush (E.g. Background/Foreground etc.) I don't need to add loads of styles to the page and all my logic is in one place - I can reuse the convertor on other xaml pages and types.

    With the Behaviour, I have to target a specific ordinal column. 6 months later someone adds a new column to the grid and you've potentially got a lot of wasted money in support time.

    So I tried the StyleSelector method (http://www.telerik.com/help/silverlight/gridview-cell-style-selector.html) - it's slightly better but a lot more code than my converter and tied to the grid. I can't use it on any other control and have to rewrite my styles to change the background of something else.

    Trying all these things just because the usual Background ValueConverter does not work has cost me a day in development time and I am not happy with any of your solutions. The core datagrid was working in 10 minutes with a Background ValueConvertor.

    But nobody's going to read this...
  • Doru Bulubasa 10 Nov 2011
    Great piece of code! Thanks TELERIK for your efforts!
    For me workes like a charm!

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)