Apply Conditional Formatting to a DataGrid with ValidationStyleDecorator
Providing users with visual cues about data validation is an important part of any interface design. The ValidationStyleDecorator in DataGrid allows you to highlight, color, add alerts, and otherwise perform both validation and conditional formatting simultaneously. It is important to note that ValidationStyleDecorator can only be applied on a bound control because it applies the style by validating data at datasource level, i.e., through the bound collection. Let's walk through the steps.
Step One: Define a style to apply to the validation
First, we need to decide what styles we'll be applying. Here, we're defining two styles to show the validated data: tooltip style and validation decorator.
Tooltip Style
<!--<br/><Setter Property="Background" Value="{StaticResource ValidationBaseColorBrush}" /><br/><Setter Property="Foreground" Value="{StaticResource ValidationForegroundBrush}" /><br/><Setter Property="Template"><br/><Setter.Value><br/><ControlTemplate TargetType="ToolTip"><br/><Grid x:Name="Root" Margin="5 0"><br/><Border Background="{TemplateBinding Background}" CornerRadius="2"><br/><ContentPresenter x:Name="Content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /><br/></Border><br/></Grid><br/></ControlTemplate><br/></Setter.Value><br/></Setter><br/>-->
Validation Decorator:
<!--<br/><Setter Property="Background" Value="{StaticResource ValidationBaseColorBrush}" /><br/><Setter Property="Foreground" Value="{StaticResource ValidationForegroundBrush}" /><br/><Setter Property="CornerRadius" Value="1" /><br/><Setter Property="BorderThickness" Value="1" /><br/><Setter Property="IsTabStop" Value="False" /><br/><Setter Property="IsHitTestVisible" Value="False" /><br/><Setter Property="Template"><br/><Setter.Value><br/><ControlTemplate TargetType="c1:C1ValidationDecorator"><br/><Border x:Name="ValidationErrorElement" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" BorderBrush="{TemplateBinding Background}" Visibility="Hidden"><br/><VisualStateManager.VisualStateGroups><br/><VisualStateGroup x:Name="ValidationStates"><br/><VisualState x:Name="Valid" /><br/><VisualState x:Name="InvalidUnfocused"><br/><Storyboard><br/><ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility"><br/><DiscreteObjectKeyFrame KeyTime="0"><br/><DiscreteObjectKeyFrame.Value><br/><Visibility>Visible </Visibility><br/></DiscreteObjectKeyFrame.Value><br/></DiscreteObjectKeyFrame><br/></ObjectAnimationUsingKeyFrames><br/></Storyboard><br/></VisualState><br/><VisualState x:Name="InvalidFocused"><br/><Storyboard><br/><ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility"><br/><DiscreteObjectKeyFrame KeyTime="0"><br/><DiscreteObjectKeyFrame.Value><br/><Visibility>Visible </Visibility><br/></DiscreteObjectKeyFrame.Value><br/></DiscreteObjectKeyFrame><br/></ObjectAnimationUsingKeyFrames><br/><ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen"><br/><DiscreteObjectKeyFrame KeyTime="0"><br/><DiscreteObjectKeyFrame.Value><br/><system:Boolean>True </system:Boolean><br/></DiscreteObjectKeyFrame.Value><br/></DiscreteObjectKeyFrame><br/></ObjectAnimationUsingKeyFrames><br/></Storyboard><br/></VisualState><br/></VisualStateGroup><br/></VisualStateManager.VisualStateGroups><br/><ToolTipService.ToolTip><br/><ToolTip x:Name="validationTooltip" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Placement="Right" PlacementTarget="{TemplateBinding Target}" Style="{StaticResource ValidationToolTipStyle}" Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Target.(Validation.Errors)}"><br/><ToolTip.ContentTemplate><br/><DataTemplate><br/><TextBlock Margin="8 4" MaxWidth="250" VerticalAlignment="Center" Text="{Binding Path=[0].ErrorContent}" TextWrapping="Wrap" /><br/></DataTemplate><br/></ToolTip.ContentTemplate><br/></ToolTip><br/></ToolTipService.ToolTip><br/><Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="{TemplateBinding Background}" Margin="0 -1 -1 0" HorizontalAlignment="Right" VerticalAlignment="Top" /><br/></Border><br/></ControlTemplate><br/></Setter.Value><br/></Setter><br/>-->
Step Two: Define Validation Conditions
So we've defined all the styles we want to apply while performing the validations. Now, we need to define our conditions. For this, we need to implement IDataErrorInfo interface:
public class Person : IDataErrorInfo
{
private int age;
private string name;
public int Age
{
get { return age; }
set { age = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Error
{
get
{return null;}
}
public string this[string name]
{
get
{
string result = null;
if (name == "Age")
{
if (this.age < 0 || this.age > 50)
{
result = "Age must be less than 50";
}
}
if (name == "Name")
{
if (this.name.Length < 3)
result = "Too Small Name";
}
return result;
}
}
}
Step Three: Apply Styles
The final step is to apply the styles using ValidationStyleDecorator.
Apply the styles in an appropriate event:
c1DataGrid1.BeganEdit += (s, e) =>
{
if (e.EditingElement is C1TextBoxBase)
{
var tb = e.EditingElement as C1TextBoxBase;
tb.ValidationDecoratorStyle = this.Resources["vd"] as Style;
var binding = tb.GetBindingExpression(C1TextBoxBase.C1TextProperty).ParentBinding;
var newbinding = CopyBinding(binding);
newbinding.NotifyOnValidationError = true;
newbinding.ValidatesOnDataErrors = true;
tb.SetBinding(C1TextBoxBase.C1TextProperty, newbinding);
}
if (e.EditingElement is C1NumericBox)
{
var nb = e.EditingElement as C1NumericBox;
var binding = nb.GetBindingExpression(C1NumericBox.ValueProperty).ParentBinding;
var newbinding = CopyBinding(binding);
newbinding.NotifyOnValidationError = true;
newbinding.ValidatesOnDataErrors = true;
nb.SetBinding(C1NumericBox.ValueProperty, newbinding);
}
};
Copy the previous binding to the updated object in order to maintain data integrity:
private Binding CopyBinding(Binding CurrentBinding)
{
Binding NewBinding = new Binding();
PropertyInfo[] fields = typeof(Binding).GetProperties();
foreach (PropertyInfo pi in fields)
{
if ((pi.GetValue(CurrentBinding, null) != null) && (pi.CanWrite == true))
pi.SetValue(NewBinding, pi.GetValue(CurrentBinding, null), null);
}
NewBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
return NewBinding;
}
Conclusion
ValidationStyleDecorator enables us to apply the custom styles to the different sub-components within a single control. Also, it provides a way to perform validation on the bound data source.