Welcome to the Xceed Community | Help
Community Search  
More Search Options

Tech Side

DataCell Styling vs CellContentTemplate

For this blog post from The Tech Side, I will be addressing a few queries that are very common when it comes to styling the DataCells and changing a Column's CellContentTemplate.

I will demonstrate this by answering the following question: If I have 2 cells, how do I color their backgrounds depending on their values?

Using the following criteria, this was my first try:

If A < B then B's background is GREEN and A's background is RED
If A > B then A's background is GREEN and B's background is RED

<Style TargetType="{x:Type xcdg:DataCell}">

   <Style.Triggers >

      <MultiDataTrigger>

         <MultiDataTrigger.Conditions>

            <Condition Binding="{Binding RelativeSource={RelativeSource self}, 
                                                           
Path
=ParentColumn.FieldName}"
                             
Value="FieldA"/>

            <Condition Binding="{Binding Converter={StaticResource compareColorConverter},
                                                            
RelativeSource={RelativeSource self}, 
                                                            
Path
=ParentRow.DataContext}" 
                              
Value
="more"/>

         </MultiDataTrigger.Conditions>

         <Setter Property="Background" Value="Green" />

      </MultiDataTrigger>

      <MultiDataTrigger>

         <MultiDataTrigger.Conditions>

            <Condition Binding="{Binding RelativeSource={RelativeSource self}, 
                                                           
Path
=ParentColumn.FieldName}"
                              
Value="FieldB"/>

            <Condition Binding="{Binding Converter={StaticResource compareColorConverter},
                                                           
RelativeSource={RelativeSource self}, 
                                                            
Path
=ParentRow.DataContext}" 
                               
Value="more"/>

         </MultiDataTrigger.Conditions>

         <Setter Property="Background" Value="Red" />

      </MultiDataTrigger>

      <MultiDataTrigger>

         <MultiDataTrigger.Conditions>

            <Condition Binding="{Binding RelativeSource={RelativeSource self}, 
                                                            
Path
=ParentColumn.FieldName}"
                              
Value="FieldA"/>

            <Condition Binding="{Binding Converter={StaticResource compareColorConverter},
                                                           
RelativeSource={RelativeSource self}, 
                                                           
Path
=ParentRow.DataContext}" 
                               
Value
="less"/>

          </MultiDataTrigger.Conditions>

         <Setter Property="Background" Value="Red" />

      </MultiDataTrigger>

      <MultiDataTrigger>

         <MultiDataTrigger.Conditions>

            <Condition Binding="{Binding RelativeSource={RelativeSource self}, 
                                                           
Path
=ParentColumn.FieldName}"
                             
Value="FieldB"/>

            <Condition Binding="{Binding Converter={StaticResource compareColorConverter},
                                                           
RelativeSource={RelativeSource self}, 
                                                           
Path
=ParentRow.DataContext}" 
                              
Value
="less"/>

         </MultiDataTrigger.Conditions>

         <Setter Property="Background" Value="Green" />

      </MultiDataTrigger>

   </Style.Triggers>

</Style>

public class CompareColorConverter : IValueConverter
{

   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      MyEntity c = value as MyEntity;

      if (c!=null)
      {
         return c.FieldA < c.FieldB ? "less" : "more";
       }

       return null;
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {

      string strValue = value as string;

       DateTime resultDateTime;

       if (DateTime.TryParse(strValue, out resultDateTime))
       {
          return resultDateTime;
       }

       return DependencyProperty.UnsetValue;
   }
}

So we have a Style that targets DataCell and uses a MultiDataTrigger with two conditions, one on the FieldName of the ParentColumn of the DataCell and the other on the DataContext of the ParentRow of the DataCell with a Converter that returns the string "less" if A is less than B and "more" if it's not. This works, the cell with the higher value between FieldA and FieldB has a red background and the other has a green background. Great! but wait... try changing the values of the cells. Notice that the colors do not update with any further changes. Why is that!? Well, it's simple, take a look at this line:

<Condition Binding="{Binding Converter={StaticResource compareColorConverter},
                                                RelativeSource
={RelativeSource self},
                                                Path
=ParentRow.DataContext}"
                                                Value
="more"/>

The binding in this condition is on DataContext of the ParentRow, which is the business object itself, and changing the value for its properties—FieldA or FieldB—would not cause any notifications for the DataContext and hence the compareColorConverter will not recalculate the value of the binding. So, this solution is only good if you know that the values for the fields are never going to be updated.

For more information about how notifications work you can check Jenny's blog post here!

So with this new information, here is my second try:

<Style TargetType="{x:Type xcdg:DataCell}">
  <Setter Property="Background">
   <Setter.Value>
    <MultiBinding Converter="{StaticResource multiValueColorConverter}">
   <Binding RelativeSource="{RelativeSource Self}" Path="ParentColumn.FieldName"/>
     <
Binding RelativeSource="{RelativeSource FindAncestor,

AncestorType={x:Type xcdg:DataRow},AncestorLevel=1}" Path="DataContext.FieldA"/>
    <Binding RelativeSource="{RelativeSource FindAncestor,

AncestorType={x:Type xcdg:DataRow},AncestorLevel=1}" Path="DataContext.FieldB"/>
    </MultiBinding>
   </Setter.Value>
   </Setter>
</Style>

public class MultiValueColorConverter : IMultiValueConverter
{

    IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
   
{
      
Brush green = Brushes.Green;
      
Brush red = Brushes.Red;
      
String fieldName = values[0] as string;
      
int? fieldA = values[1] as int?;
      
int? fieldB = values[2] as int?;

       if (fieldA != null && fieldB != null)
      
{
         
if (fieldName.Equals("FieldA"))
          
{
            
if (fieldA < fieldB)
              
return red;

             return green;
         
}
         
else if (fieldName.Equals("FieldB"))
         
{
             i
f (fieldA < fieldB)
               
return green;

             return red;
         
}
       }

       
return null;
   }

   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
   {
     
throw new NotImplementedException();
   }
}

In this case, we also have a Style that targets DataCell, we use a MultiBinding to set the Value of the DataCell's Background property. The MultiBinding binds on the FieldName, the value of FieldA and the value of FieldB. The MultiValueConverter returns either a Green or a Red brush depending on the values of FieldA and FieldB and the FieldName passed to the converter. This does work even when you change the values of FieldA and FieldB at runtime, this is mainly because the binding binds directly to "FieldA" and "FieldB" properties directly and any changes would directly be notified and the converter would recalculate the binding.

And just for the fun of it, this is a third way to do this but this time through the CellContentTemplate property of the Column.

<xcdg:DataGridControl.Columns>
   <xcdg:Column FieldName="FieldA">
  
   <xcdg:Column.CellContentTemplate>
        
<DataTemplate>
            
<TextBlock Text="{Binding}">
             
<TextBlock.Background>
               
<MultiBinding Converter="{StaticResource compareColorConverter}"

ConverterParameter='FieldA'>
                  
<Binding Path="." />
                   
<Binding RelativeSource="{RelativeSource FindAncestor,

AncestorType={x:Type xcdg:Cell}}" Path="DataContext.FieldB" />
                </MultiBinding>
           
</TextBlock.Background>
        
</TextBlock>
     
</DataTemplate>
  
</xcdg:Column.CellContentTemplate>
</xcdg:Column>

<xcdg:Column FieldName="FieldB">
   
<xcdg:Column.CellContentTemplate>
      
<DataTemplate>
         
<TextBlock Text="{Binding}">
            
<TextBlock.Background>
               
<MultiBinding Converter="{StaticResource compareColorConverter}"

, ConverterParameter='FieldB'>
                  
<Binding RelativeSource="{RelativeSource FindAncestor,

AncestorType={x:Type xcdg:Cell}}", Path="DataContext.FieldA" />
                  
<Binding Path="." />
               
</MultiBinding>
            
</TextBlock.Background>
         
</TextBlock>
     
</DataTemplate>
  
</xcdg:Column.CellContentTemplate>
 </xcdg:Column>
</xcdg:DataGridControl.Columns>

You can download the source code for the previous three samples by clicking here.

Published July 6, 2011 2:04 PM by Michel [Xceed]

Comments

 

thomas said:

Nice summary of the options. Of these three methods, which one performs the best?

March 20, 2012 1:02 AM
 

Jordan said:

There's a small problem with your MultiValueColorConverter code: the values in the "values" array may be null.  In the code files you've attached, you include an "if" code block that checks for null values.  You're missing that in this blog post however.

At the top of the Convert function, you need:

if (values[0] != null && values[1] != null)

April 11, 2012 8:33 AM
Anonymous comments are disabled
Contact | Site Map | Reviews | Legal Terms of Use | Trademarks | Privacy Statement Copyright 2011 Xceed Software Inc.