Data Validation in WPF DataGrid (SfDataGrid)

12 Sep 202324 minutes to read

WPF DataGrid (SfDataGrid) allows you to validate the data and display hints in case of validation is not passed. In case of invalid data, error icon is displayed at the top right corner of GridCell. When mouse over the error icon, error information will be displayed in tooltip.

Built-in validations

Built-in validations through IDataErrorInfo, INotifyDataErrorInfo and Data annotation attributes, can be enabled by setting SfDataGrid.GridValidationMode or GridColumn.GridValidationMode properties.

GridColumn.GridValidationMode takes priority than SfDataGrid.GridValidationMode.

  • GridValidation.InEdit - display error icon & tips and also doesn’t allows the users to commit the invalid data without allowing users to edit other cells.
  • GridValidation.InView - displays error icons and tips alone.
  • GridValidation.None - disables built-in validation support.

Built-in validation using IDataErrorInfo / INotifyDataErrorInfo

WPF DataGrid (SfDataGrid) provides support to validate the data based on IDataErrorInfo / INotifyDataErrorInfo.

Using IDataErrorInfo

You can validate the data by inheriting the IDataErrorInfo interface in model class.

public class OrderInfo : IDataErrorInfo 
{
    private string country;

    public string Country
    {
        get { return country; }
        set { country = value; }
    }     

    [Display(AutoGenerateField = false)]

    public string Error
    {
        get
        {
            return string.Empty;
        }
    }

    public string this[string columnName]
    {
        get 
        {

            if (!columnName.Equals("Country"))
                return string.Empty;

            if (this.Country.Contains("Germany") || this.Country.Contains("UK"))
                return "Delivery not available for the country " + this.Country;

            return string.Empty;
        }
    }        
}

Enable built-in validation support by setting SfDataGrid.GridValidationMode or GridColumn.GridValidationMode property to InEdit or InView.

<syncfusion:SfDataGrid x:Name="dataGrid" 
                       ItemsSource="{Binding Orders}"                        
                       AllowEditing="True"                           
                       GridValidationMode="InView"/>
this.dataGrid.GridValidationMode = GridValidationMode.InView;

Data validation in WPF DataGrid using IDataErrorInfo

INotifyDataErrorInfo

You can validate the data by inheriting the INotifyDataErrorInfo interface in model class.

public class OrderInfo : INotifyDataErrorInfo
{
    private List<string> errors = new List<string>();    

    private string shippingCity;

    public string ShipCity
    {
        get { return shippingCity; }
        set { shippingCity = value; }
    }

    public System.Collections.IEnumerable GetErrors(string propertyName)
    {

        if (!propertyName.Equals("ShipCity"))
            return null;

        if (this.ShipCity.Contains("Mexico D.F."))
            errors.Add("Delivery not available for the city " + this.ShipCity);

        return errors;
    }

    [Display(AutoGenerateField = false)]

    public bool HasErrors
    {
        get
        {
            return false;
        }
    }
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}

Enable built-in validation support by setting SfDataGrid.GridValidationMode or GridColumn.GridValidationMode property to InEdit or InView.

<syncfusion:SfDataGrid x:Name="dataGrid" 
                        ItemsSource="{Binding Orders}"                                                    
                        AllowEditing="True"                                                
                        GridValidationMode="InView"/>

Data validation in WPF DataGrid using INotifyDataErrorInfo

Built-in validation using Data Annotation

You can validate the data using data annotation attributes by setting SfDataGrid.GridValidationMode or GridColumn.GridValidationMode property to InEdit or InView.

Using different annotations

The numeric type like int, double, decimal properties can be validated using Range attributes.

private int orderID;
[Range(1001, 1005, ErrorMessage = "OrderID between 1001 and 1005 alone processed")]        

public int OrderID
{
    get { return orderID; }
    set { orderID = value; }
}

private decimal price;
[Range(typeof(decimal),"12","20")]

public decimal Price
{
    get { return price; }
    set { price = value; }
}

The string type property can be validated using Required, String Length attributes

private string shippingCity;
[Required]

public string ShipCity
{
    get { return shippingCity; }
    set { shippingCity = value; }
}

private string customerName;
[StringLength(17)]

public string CustomerName
{
    get { return customerName; }
    set { customerName = value; }
}

The data that has heterogeneous type (combination of number, special character) can be validated using RegularExpressions.

[RegularExpressionAttribute(@"^[a-zA-Z]{1,40}$", ErrorMessage="Numbers and special characters not allowed")]

public string CustomerID
{
    get { return customerId; }
    set { customerId = value; }
}

Cell validation

You can validate the cells using CurrentCellValidating event when the cell is edited. CurrentCellValidating event occurs when the edited cells tries to commit the data or lose the focus. DataGrid will not allow user to edit other cells if validation failed.

CurrentCellValidatingEventArgs provides information to CurrentCellValidating event for validating the cell. CurrentCellValidatingEventArgs.OriginalSender returns the DataGrid fired this event for DetailsView.

CurrentCellValidatingEventArgs.NewValue returns the edited value and you can set the validation status using CurrentCellValidatingEventArgs.IsValid property.

this.dataGrid.CurrentCellValidating += dataGrid_CurrentCellValidating;

void dataGrid_CurrentCellValidating(object sender, CurrentCellValidatingEventArgs args)
{

    if (args.NewValue.ToString().Equals("1004"))
    {
        args.IsValid = false;
        args.ErrorMessage = "OrderID 1004 cannot be passed";
    }
}

SfDataGrid.CurrentCellValidated event triggered when the cell has finished validating with valid data.

this.dataGrid.CurrentCellValidated += dataGrid_CurrentCellValidated;

void dataGrid_CurrentCellValidated(object sender, CurrentCellValidatedEventArgs args)
{
}

Row validation

You can validate the row using RowValidating event when the cell is edited. The RowValidating event occurs when the edited cells tries to commit the row data or lose the focus. DataGrid will not allow user to edit other rows if validation failed.

RowValidatingEventArgs provides information to RowValidating event for validating row. RowValidatingEventArgs.OriginalSender returns the DataGrid fired this event for DetailsView.

RowValidatingEventArgs.RowData returns the edited value and you can set the validation status using RowValidatingEventArgs.IsValid property.

this.dataGrid.RowValidating += dataGrid_RowValidating;

void dataGrid_RowValidating(object sender, RowValidatingEventArgs args)
{
    var data = args.RowData.GetType().GetProperty("CustomerID").GetValue(args.RowData);

    if(data.ToString().Equals("AROUT"))
    {                
        args.IsValid = false;
        args.ErrorMessages.Add("CustomerID", "Customer AROUT cannot be passed");
    }
}

SfDataGrid.RowValidated event triggered when the row has finished validating with valid row data.

this.dataGrid.RowValidated += dataGrid_RowValidated;   

void dataGrid_RowValidated(object sender, RowValidatedEventArgs args)
{
}

Data validation error icon customization

You can customize the error icon by editing GridCell style.

Change the shape of error icon

You can change the validation error template shape of the GridCell by changing the Data property of the path in the PART_InValidCellBorder of GridCell.

<Style TargetType="{x:Type syncfusion:GridCell}">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="BorderBrush" Value="Gray" />
    <Setter Property="BorderThickness" Value="0,0,1,1" />
    <Setter Property="Padding" Value="0,0,0,0" />
    <Setter Property="FocusVisualStyle" Value="{x:Null}" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type syncfusion:GridCell}">
                <Grid SnapsToDevicePixels="True">
                    <VisualStateManager.VisualStateGroups>
                    
                        <VisualStateGroup x:Name="IndicationStates">  
                                              
                            <VisualState x:Name="HasError">                            
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="Width">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="0" Value="10" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                   Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                            <VisualState x:Name="NoError">
                                <Storyboard BeginTime="0">
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="Width">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                   Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>  
                                                      
                        </VisualStateGroup>
                        
                        <VisualStateGroup x:Name="BorderStates">
                        
                            <VisualState x:Name="NormalCell"/>
                            
                            <VisualState x:Name="FrozenColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											Value="0,0,1,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>                                
                            </VisualState>
                            
                            <VisualState x:Name="FooterColumnCell">                            
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="1,0,1,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                            <VisualState x:Name="BeforeFooterColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="0,0,0,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                        </VisualStateGroup>
                                                
                    </VisualStateManager.VisualStateGroups>
                    
                    <Border Background="{TemplateBinding CellSelectionBrush}"
						    SnapsToDevicePixels="True"
						    Visibility="{TemplateBinding SelectionBorderVisibility}" />
                            
                    <Border x:Name="PART_GridCellBorder"
						    Background="{TemplateBinding Background}"
						    BorderBrush="{TemplateBinding BorderBrush}"
						    BorderThickness="{TemplateBinding BorderThickness}"
						    SnapsToDevicePixels="True">
                        <Grid>
                            <ContentPresenter Margin="{TemplateBinding Padding}" />
                        </Grid>
                    </Border>
                    
                    <Border Background="Transparent"
						    BorderBrush="{TemplateBinding CurrentCellBorderBrush}"
						    BorderThickness="{TemplateBinding CurrentCellBorderThickness}"
						    IsHitTestVisible="False"
						    SnapsToDevicePixels="True"
						    Margin="0,0,1,1"
						    Visibility="{TemplateBinding CurrentCellBorderVisibility}" />
                            
                    <Border x:Name="PART_InValidCellBorder"
						    Width="10"
						    Height="10"
						    HorizontalAlignment="Right"
						    Visibility="Collapsed"
						    VerticalAlignment="Top"
						    SnapsToDevicePixels="True">                            
                        <ToolTipService.ToolTip>
                            <ToolTip Background="#FFDB000C"
								     Placement="Right"
								     PlacementRectangle="20,0,0,0"
								     Tag="{TemplateBinding ErrorMessage}"
								     Template="{StaticResource ValidationToolTipTemplate}" />
                        </ToolTipService.ToolTip>                        
                        <Path Data="M15.396557,23.044006C14.220558,23.044006 13.268559,23.886993 13.268559,24.927994 13.268559,25.975006 14.220558,26.817001 15.396557,26.817001 16.572557,26.817001 17.523547,25.975006 17.523547,24.927994 17.523547,23.886993 16.572557,23.044006 15.396557,23.044006z M15.467541,5.1819992C15.447552,5.1819992 15.436566,5.1829987 15.436566,5.1829987 13.118533,5.5049973 13.055545,7.3330002 13.055545,7.3330002L13.055545,9.2929993 13.626531,16.539001C13.983558,18.357002 14.243538,19.020004 14.243538,19.020004 15.275555,19.975006 16.203567,19.25 16.203567,19.25 16.976548,18.565994 17.028552,16.962997 17.028552,16.962997 17.956563,9.2929993 17.696553,7.1029968 17.696553,7.1029968 17.608571,5.2839966 15.823561,5.1849976 15.490551,5.1819992 15.481549,5.1819992 15.473553,5.1819992 15.467541,5.1819992z M15.56355,0C15.56355,0 21.710574,4.1259995 31.581613,2.8030014 31.581613,2.8030014 33.634629,26.556992 15.56355,32 15.56355,32 -0.10249132,27.548004 0.00050565118,2.9670029 0.0005058694,2.9670029 10.72555,3.6309967 15.56355,0z"
                             Fill="Red"                                     
                             SnapsToDevicePixels="True"
                             Stretch="Fill" />                             
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

WPF DataGrid displays Data Validation Errors with Error Icon

Change the color of error icon

You can change the validation error template color of the GridCell by changing the Fill property of the path in the PART_InValidCellBorder of GridCell.

<Style TargetType="{x:Type syncfusion:GridCell}">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="BorderBrush" Value="Gray" />
    <Setter Property="BorderThickness" Value="0,0,1,1" />
    <Setter Property="Padding" Value="0,0,0,0" />
    <Setter Property="FocusVisualStyle" Value="{x:Null}" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type syncfusion:GridCell}">
                <Grid SnapsToDevicePixels="True">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="IndicationStates">

                            <VisualState x:Name="HasError">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="Width">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="0" Value="10" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                   Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="NoError">
                                <Storyboard BeginTime="0">
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="Width">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                   Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                        </VisualStateGroup>
                        
                        <VisualStateGroup x:Name="BorderStates">
                            <VisualState x:Name="NormalCell"/>
                            <VisualState x:Name="FrozenColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="0,0,1,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                            <VisualState x:Name="FooterColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="1,0,1,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                            <VisualState x:Name="BeforeFooterColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="0,0,0,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                        </VisualStateGroup>
                        
                    </VisualStateManager.VisualStateGroups>
                    
                    <Border Background="{TemplateBinding CellSelectionBrush}"
						    SnapsToDevicePixels="True"
						    Visibility="{TemplateBinding SelectionBorderVisibility}" />

                    <Border x:Name="PART_GridCellBorder"
						    Background="{TemplateBinding Background}"
						    BorderBrush="{TemplateBinding BorderBrush}"
						    BorderThickness="{TemplateBinding BorderThickness}"
						   SnapsToDevicePixels="True">

                        <Grid>
                            <ContentPresenter Margin="{TemplateBinding Padding}" />
                        </Grid>

                    </Border>

                    <Border Background="Transparent"
						    BorderBrush="{TemplateBinding CurrentCellBorderBrush}"
						    BorderThickness="{TemplateBinding CurrentCellBorderThickness}"
						    IsHitTestVisible="False"
						    SnapsToDevicePixels="True"
						    Margin="0,0,1,1"
						    Visibility="{TemplateBinding CurrentCellBorderVisibility}" />
                    <Border x:Name="PART_InValidCellBorder"
						    Width="10"
						    Height="10"
						    HorizontalAlignment="Right"
						    Visibility="Collapsed"
						    VerticalAlignment="Top"
						    SnapsToDevicePixels="True">
                        <ToolTipService.ToolTip>
                            <ToolTip Background="#FFDB000C"
								     Placement="Right"
								     PlacementRectangle="20,0,0,0"
								     Tag="{TemplateBinding ErrorMessage}"
								     Template="{StaticResource ValidationToolTipTemplate}" />
                        </ToolTipService.ToolTip>
                        
                        <Path Data="M0.5,0.5 L12.652698,0.5 12.652698,12.068006 z"
	                          Fill="Orange"
	                          SnapsToDevicePixels="True"
	                          Stretch="Fill" />
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Data Validation Error Icon with Custom Color in WPF DataGrid

Change the cursor over error icon

You can change the validation error template cursor of the GridCell by changing the Cursor property of the path codes in the PART_InValidCellBorder of GridCell.

<Style TargetType="{x:Type syncfusion:GridCell}">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="BorderBrush" Value="Gray" />
    <Setter Property="BorderThickness" Value="0,0,1,1" />
    <Setter Property="Padding" Value="0,0,0,0" />
    <Setter Property="FocusVisualStyle" Value="{x:Null}" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type syncfusion:GridCell}">
                <Grid SnapsToDevicePixels="True">
                    <VisualStateManager.VisualStateGroups>
                    
                        <VisualStateGroup x:Name="IndicationStates">

                            <VisualState x:Name="HasError">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="Width">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="0" Value="10" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                   Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="NoError">
                                <Storyboard BeginTime="0">
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="Width">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                   Storyboard.TargetName="PART_InValidCellBorder" 
                                                                   Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                        </VisualStateGroup>
                        
                        <VisualStateGroup x:Name="BorderStates">
                        
                            <VisualState x:Name="NormalCell"/>
                            
                            <VisualState x:Name="FrozenColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="0,0,1,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                            <VisualState x:Name="FooterColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="1,0,1,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                            <VisualState x:Name="BeforeFooterColumnCell">
                                <Storyboard BeginTime="0">
                                    <ThicknessAnimationUsingKeyFrames BeginTime="0"
										                              Duration="1"
										                              Storyboard.TargetName="PART_GridCellBorder"
										                              Storyboard.TargetProperty="BorderThickness">
                                        <EasingThicknessKeyFrame KeyTime="0"
											                     Value="0,0,0,1"/>
                                    </ThicknessAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            
                        </VisualStateGroup>
                        
                    </VisualStateManager.VisualStateGroups>
                    
                    <Border Background="{TemplateBinding CellSelectionBrush}"
						    SnapsToDevicePixels="True"
						    Visibility="{TemplateBinding SelectionBorderVisibility}" />

                    <Border x:Name="PART_GridCellBorder"
						    Background="{TemplateBinding Background}"
						    BorderBrush="{TemplateBinding BorderBrush}"
						    BorderThickness="{TemplateBinding BorderThickness}"
						    SnapsToDevicePixels="True">

                        <Grid>
                            <ContentPresenter Margin="{TemplateBinding Padding}" />
                        </Grid>

                    </Border>

                    <Border Background="Transparent"
						    BorderBrush="{TemplateBinding CurrentCellBorderBrush}"
						    BorderThickness="{TemplateBinding CurrentCellBorderThickness}"
						    IsHitTestVisible="False"
						    SnapsToDevicePixels="True"
						    Margin="0,0,1,1"
						    Visibility="{TemplateBinding CurrentCellBorderVisibility}" />
                            
                    <Border x:Name="PART_InValidCellBorder"
						    Width="10"
						    Height="10"
						    HorizontalAlignment="Right"
						    Visibility="Collapsed"
						    VerticalAlignment="Top"
						    SnapsToDevicePixels="True">
                        <ToolTipService.ToolTip>
                            <ToolTip Background="#FFDB000C"
								     Placement="Right"
								     PlacementRectangle="20,0,0,0"
								     Tag="{TemplateBinding ErrorMessage}"
								     Template="{StaticResource ValidationToolTipTemplate}" />
                        </ToolTipService.ToolTip>
                        
                        <Path Data="M0.5,0.5 L12.652698,0.5 12.652698,12.068006 z"
                              Fill="Red"
                              SnapsToDevicePixels="True"
                              Cursor="Hand"
                              Stretch="Fill" />

                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Changing Cursor Style on Error Icon in WPF DataGrid

Data validation error tip (help tip) customization

You can customize the error tip by editing the style of ValidationToolTipTemplate. Get the style of ValidationToolTipTemplate by editing the GridCell style.

Change the background and foreground color of error tip

You can change the error tip background color by setting Background property of the border in ValidationToolTipTemplate. The error tip foreground color can be changed by setting Foreground property of the TextBlock in ValidationToolTipTemplate.

<ControlTemplate x:Key="ValidationToolTipTemplate">
    <Grid x:Name="Root"
		  Margin="5,0"
		  Opacity="0"
		  RenderTransformOrigin="0,0">
        <Grid.RenderTransform>
            <TranslateTransform x:Name="xForm" X="-25" />
        </Grid.RenderTransform>
        <VisualStateManager.VisualStateGroups>
        
            <VisualStateGroup x:Name="OpenStates">
            
                <VisualStateGroup.Transitions>
                
                    <VisualTransition GeneratedDuration="0" />
                    
                    <VisualTransition GeneratedDuration="0:0:0.2" To="Open">
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:0.2"
								             Storyboard.TargetName="xForm"
								             Storyboard.TargetProperty="X"
								             To="0">
                                <DoubleAnimation.EasingFunction>
                                    <BackEase Amplitude=".3" EasingMode="EaseOut" />
                                </DoubleAnimation.EasingFunction>
                            </DoubleAnimation>
                            <DoubleAnimation Duration="0:0:0.2"
								Storyboard.TargetName="Root"
								Storyboard.TargetProperty="Opacity"
								To="1" />
                        </Storyboard>
                    </VisualTransition>
                </VisualStateGroup.Transitions>
                
                <VisualState x:Name="Closed">
                    <Storyboard>
                        <DoubleAnimation Duration="0"
							             Storyboard.TargetName="Root"
							             Storyboard.TargetProperty="Opacity"
							             To="0" />
                    </Storyboard>
                </VisualState>
                
                <VisualState x:Name="Open">
                    <Storyboard>
                        <DoubleAnimation Duration="0"
							             Storyboard.TargetName="xForm"
							             Storyboard.TargetProperty="X"
							             To="0" />
                        <DoubleAnimation Duration="0"
							             Storyboard.TargetName="Root"
							             Storyboard.TargetProperty="Opacity"
							             To="1" />
                    </Storyboard>
                </VisualState>
                
            </VisualStateGroup>
            
        </VisualStateManager.VisualStateGroups>

        <Border Margin="4,4,-4,-4"
			    Background="#052A2E31"
			    CornerRadius="5" />
        <Border Margin="3,3,-3,-3"
			    Background="#152A2E31"
			    CornerRadius="4" />
        <Border Margin="2,2,-2,-2"
			    Background="#252A2E31"
			    CornerRadius="3" />
        <Border Margin="1,1,-1,-1"
			    Background="#352A2E31"
			    CornerRadius="2" />

        <Border Background="Orange" CornerRadius="2" />
        <Border CornerRadius="2">
            <TextBlock MaxWidth="250"
                       Margin="8,4,8,4"
                       Foreground="Black"
                       Text="{TemplateBinding Tag}"
                       TextWrapping="Wrap"
                       UseLayoutRounding="false" />
        </Border>
    </Grid>
</ControlTemplate>

WPF DataGrid - Error Tip Foreground and Background Customization

Showing error details in RowHeader

WPF DataGrid (SfDataGrid) support to show the error icon in GridRowHeaderCell based on IDataErrorInfo.Error or INotifyDataErrorInfo.HasErrors property.

Using IDataErrorInfo

You can show the error information in row header by setting IDataErrorInfo.Error. IDataErrorInfo.Error will be displayed as error message in tooltip.

[Display(AutoGenerateField = false)]

public string Error
{
    get
    {

        if (this.Country.Contains("Germany") || this.Country.Contains("UK"))
            return "Delivery not available for the country " + this.Country;

        return string.Empty;
    }
}

WPF DataGrid displays Error Information of Data Validation using IDataErrorInfo

Using INotifyDataErrorInfo

You can show the error information in row header by setting INotifyDataErrorInfo.HasErrors. By default error message Row Containing Error will be displayed. You can change this by changing RowErrorMessage in the resx file.

[Display(AutoGenerateField = false)]

public bool HasErrors
{
    get
    {

        if (this.ShipCity.Contains("Mexico D.F."))
            return true;
        return false;
    }
}

WPF DataGrid displays Error Information of Data Validation using INotifyDataErrorInfo

Data validation with Master-details view

Master-Details View allows you to validate the bound data is valid or not.
You can do both built-in and custom validation of data in DetailsViewDataGrid.

Built-in validations

You can validate the bound data based on IDataErrorInfo / INotifyDataErrorInfo or Data Annotation Attributes by setting GridValidationMode property of ViewDefinition.DataGrid.

<syncfusion:SfDataGrid x:Name="dataGrid"                                  
                       NavigationMode="Cell"
                       AutoGenerateColumns="True"                              
                       ItemsSource="{Binding Orders}">
    <syncfusion:SfDataGrid.DetailsViewDefinition>
        <syncfusion:GridViewDefinition RelationalColumn="ProductDetails">
            <syncfusion:GridViewDefinition.DataGrid>
                <syncfusion:SfDataGrid x:Name="FirstLevelNestedGrid" 
                                       GridValidationMode="InView"                                                                                         
                                       AutoGenerateColumns="True">                                                                                  
                </syncfusion:SfDataGrid>
            </syncfusion:GridViewDefinition.DataGrid>
        </syncfusion:GridViewDefinition>
    </syncfusion:SfDataGrid.DetailsViewDefinition>
</syncfusion:SfDataGrid>

When the relation is auto-generated, the data can be validated by setting GridValidationMode property to AutoGeneratingRelations.GridViewDefinition.DataGrid in AutoGeneratingRelations event handler.

dataGrid.AutoGenerateRelations = true;

dataGrid.AutoGeneratingRelations +=dataGrid_AutoGeneratingRelations;

void dataGrid_AutoGeneratingRelations(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingRelationsArgs e)
{
    e.GridViewDefinition.DataGrid.GridValidationMode = GridValidationMode.InView;
}

Data Validation with Master-DetailsView in WPF SfDataGrid

Custom validation through events

Master-Details View support to validate the cells and rows using CurrentCellValidating and RowValidating events.

Cell Validation

You can validate the cells using CurrentCellValidating event of ViewDefinition.DataGrid when the cell is edited. CurrentCellValidating event occurs when the edited cells tries to commit the data or lose the focus.

<syncfusion:SfDataGrid x:Name="dataGrid"                                  
                       NavigationMode="Cell"                           
                       AutoGenerateColumns="True"                              
                       ItemsSource="{Binding Orders}">
    <syncfusion:SfDataGrid.DetailsViewDefinition>
        <syncfusion:GridViewDefinition RelationalColumn="ProductDetails">
            <syncfusion:GridViewDefinition.DataGrid>
                <syncfusion:SfDataGrid x:Name="FirstLevelNestedGrid"  
                                       AllowEditing="True"                                                                  CurrentCellValidating="FirstLevelNestedGrid_CurrentCellValidating"                                       
                                       AutoGenerateColumns="True">                                                                                                      
                </syncfusion:SfDataGrid>
            </syncfusion:GridViewDefinition.DataGrid>
        </syncfusion:GridViewDefinition>
    </syncfusion:SfDataGrid.DetailsViewDefinition>
</syncfusion:SfDataGrid>


this.FirstLevelNestedGrid.CurrentCellValidating +=FirstLevelNestedGrid_CurrentCellValidating;

private void FirstLevelNestedGrid_CurrentCellValidating(object sender, CurrentCellValidatingEventArgs args)
{

    if(args.NewValue.ToString().Equals("Bike2"))
    {
        args.IsValid = false;
        args.ErrorMessage = "Order ID 1002 not ordered Bike2";
    }
}

CurrentCellValidated event ViewDefinition.DataGrid triggered when the cell has finished validating with valid data

<syncfusion:SfDataGrid x:Name="dataGrid"                                  
                       NavigationMode="Cell"                                                 
                       AutoGenerateColumns="True"                              
                       ItemsSource="{Binding Orders}">
    <syncfusion:SfDataGrid.DetailsViewDefinition>
        <syncfusion:GridViewDefinition RelationalColumn="ProductDetails">
            <syncfusion:GridViewDefinition.DataGrid>
                <syncfusion:SfDataGrid x:Name="FirstLevelNestedGrid"  
                                       AllowEditing="True"                                                                              CurrentCellValidated="FirstLevelNestedGrid_CurrentCellValidated"                                       
                                       AutoGenerateColumns="True">                                                                                  
                </syncfusion:SfDataGrid>
            </syncfusion:GridViewDefinition.DataGrid>
        </syncfusion:GridViewDefinition>
    </syncfusion:SfDataGrid.DetailsViewDefinition>
</syncfusion:SfDataGrid>


this.FirstLevelNestedGrid.CurrentCellValidated +=FirstLevelNestedGrid_CurrentCellValidated;

private void FirstLevelNestedGrid_CurrentCellValidated(object sender, CurrentCellValidatedEventArgs args)
{

}

When the relation is auto-generated, you can wire the CurrentCellValidating and CurrentCellValidated events for AutoGeneratingRelations.GridViewDefinition.DataGrid in AutoGeneratingRelations event handler.

dataGrid.AutoGenerateRelations = true;
dataGrid.AutoGeneratingRelations +=dataGrid_AutoGeneratingRelations;

void dataGrid_AutoGeneratingRelations(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingRelationsArgs e)
{
    e.GridViewDefinition.DataGrid.CurrentCellValidating += FirstLevelNestedGrid_CurrentCellValidating;
    e.GridViewDefinition.DataGrid.CurrentCellValidated += FirstLevelNestedGrid_CurrentCellValidated;
}

Row Validation

You can validate the row using RowValidating event of ViewDefinition.DataGrid when the cell is edited.

The RowValidating event occurs when edited cells tries to commit the row data or lose the focus.

<syncfusion:SfDataGrid x:Name="dataGrid"                                  
                       NavigationMode="Cell"
                       AutoGenerateColumns="True"                              
                       ItemsSource="{Binding Orders}">
    <syncfusion:SfDataGrid.DetailsViewDefinition>
        <syncfusion:GridViewDefinition RelationalColumn="ProductDetails">
            <syncfusion:GridViewDefinition.DataGrid>
                <syncfusion:SfDataGrid x:Name="FirstLevelNestedGrid"  
                                       AllowEditing="True"                                                                              RowValidating="FirstLevelNestedGrid_RowValidating"
                                       AutoGenerateColumns="True">                                                                                  
                </syncfusion:SfDataGrid>
            </syncfusion:GridViewDefinition.DataGrid>
        </syncfusion:GridViewDefinition>
    </syncfusion:SfDataGrid.DetailsViewDefinition>
</syncfusion:SfDataGrid>


this.FirstLevelNestedGrid.RowValidating +=FirstLevelNestedGrid_RowValidating;

private void FirstLevelNestedGrid_RowValidating(object sender, RowValidatingEventArgs args)
{
    var data = args.RowData.GetType().GetProperty("ProductName").GetValue(args.RowData);

    if (data.ToString().Equals("Bike1"))
    {
        args.IsValid = false;
        args.ErrorMessages.Add("ProductName", "Product Bike 1 cannot be order for OrderID 1002");
    }
}

RowValidated of ViewDefinition.DataGrid event triggered when the row has finished validating with valid row data.

<syncfusion:SfDataGrid x:Name="dataGrid"                                  
                       NavigationMode="Cell"
                       AutoGenerateColumns="True"                              
                       ItemsSource="{Binding Orders}">
    <syncfusion:SfDataGrid.DetailsViewDefinition>
        <syncfusion:GridViewDefinition RelationalColumn="ProductDetails">
            <syncfusion:GridViewDefinition.DataGrid>
                <syncfusion:SfDataGrid x:Name="FirstLevelNestedGrid"  
                                       AllowEditing="True"                                                                                                                    
                                       RowValidated="FirstLevelNestedGrid_RowValidated"
                                       AutoGenerateColumns="True">                                                                                                  </syncfusion:SfDataGrid>
            </syncfusion:GridViewDefinition.DataGrid>
        </syncfusion:GridViewDefinition>
    </syncfusion:SfDataGrid.DetailsViewDefinition>
</syncfusion:SfDataGrid>


this.FirstLevelNestedGrid.RowValidated +=FirstLevelNestedGrid_RowValidated;

private void FirstLevelNestedGrid_RowValidated(object sender, RowValidatedEventArgs args)
{

}

When the relation is auto-generated, you can wire the RowValidating and RowValidated events for AutoGeneratingRelations.GridViewDefinition.DataGrid in AutoGeneratingRelations event handler.

dataGrid.AutoGenerateRelations = true;
dataGrid.AutoGeneratingRelations +=dataGrid_AutoGeneratingRelations;

void dataGrid_AutoGeneratingRelations(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingRelationsArgs e)
{
    e.GridViewDefinition.DataGrid.RowValidating += FirstLevelNestedGrid_RowValidating;
    e.GridViewDefinition.DataGrid.RowValidated += FirstLevelNestedGrid_RowValidated;
}

Data validation with checkbox column

SfDataGrid doesn’t support to validate the GridCheckBoxColumn through validating events.

You can validate the check box column value by setting ValidationHelper.IsCurrentCellValidated and ValidationHelper.IsCurrentRowValidated static properties by calling SetCurrentRowValidated and SetCurrentCellValidated methods from ValidationHelper.

using Syncfusion.UI.Xaml.Grid.Helpers;

this.dataGrid.CurrentCellValueChanged += dataGrid_CurrentCellValueChanged;

void dataGrid_CurrentCellValueChanged(object sender, CurrentCellValueChangedEventArgs args)
{
    int columnIndex = this.dataGrid.ResolveToGridVisibleColumnIndex(args.RowColumnIndex.ColumnIndex);

    //We are enabling the RowValidating, CellValidating event if the changes happen in GridCheckBoxColumn

    if (this.dataGrid.Columns[columnIndex].CellType == "CheckBox")
    {
        this.dataGrid.GetValidationHelper().SetCurrentRowValidated(false);
        this.dataGrid.GetValidationHelper().SetCurrentCellValidated(false);
    }
}

WPF DataGrid displays Data Validation with CheckBox Column

Show validation errors when using UseDrawing

By default, validation is not supported while enabling the UseDrawing property since the cell content were drawn instead of loading the UIElement. However, SfDataGrid provides an option to achieve the validation by adding the validation template.

Please refer the below code example for further details about achieving Validation when using UseDrawing property.

<Window.Resources>
        <ControlTemplate x:Key="ValidationToolTipTemplate">
            <Grid x:Name="Root"
              Margin="5,0"
              Opacity="0"
              RenderTransformOrigin="0,0">
                <Grid.RenderTransform>
                    <TranslateTransform x:Name="xform" X="-25" />
                </Grid.RenderTransform>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup Name="OpenStates">
                        <VisualStateGroup.Transitions>
                            <VisualTransition GeneratedDuration="0" />
                            <VisualTransition GeneratedDuration="0:0:0.2" To="Open">
                                <Storyboard>
                                    <DoubleAnimation Duration="0:0:0.2"
                                                 Storyboard.TargetName="xform"
                                                 Storyboard.TargetProperty="X"
                                                 To="0">
                                        <DoubleAnimation.EasingFunction>
                                            <BackEase Amplitude=".3" EasingMode="EaseOut" />
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                    <DoubleAnimation Duration="0:0:0.2"
                                                 Storyboard.TargetName="Root"
                                                 Storyboard.TargetProperty="Opacity"
                                                 To="1" />
                                </Storyboard>
                            </VisualTransition>
                        </VisualStateGroup.Transitions>
                        <VisualState x:Name="Closed">
                            <Storyboard>
                                <DoubleAnimation Duration="0"
                                             Storyboard.TargetName="Root"
                                             Storyboard.TargetProperty="Opacity"
                                             To="0" />
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Open">
                            <Storyboard>
                                <DoubleAnimation Duration="0"
                                             Storyboard.TargetName="xform"
                                             Storyboard.TargetProperty="X"
                                             To="0" />
                                <DoubleAnimation Duration="0"
                                             Storyboard.TargetName="Root"
                                             Storyboard.TargetProperty="Opacity"
                                             To="1" />
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>

                <Border Margin="4,4,-4,-4"
                    Background="#052A2E31"
                    CornerRadius="5" />
                <Border Margin="3,3,-3,-3"
                    Background="#152A2E31"
                    CornerRadius="4" />
                <Border Margin="2,2,-2,-2"
                    Background="#252A2E31"
                    CornerRadius="3" />
                <Border Margin="1,1,-1,-1"
                    Background="#352A2E31"
                    CornerRadius="2" />

                <Border Background="#FFDC000C" CornerRadius="2" />
                <Border CornerRadius="2">
                    <TextBlock MaxWidth="250"
                           Margin="8,4,8,4"
                           Foreground="White"
                           Text="{TemplateBinding Tag}"
                           TextWrapping="Wrap"
                           UseLayoutRounding="false" />
                </Border>
            </Grid>
        </ControlTemplate>
        <ControlTemplate x:Key="GridCellControlTemplate" TargetType="Syncfusion:GridCell">
            <Grid SnapsToDevicePixels="True">
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="IndicationStates">
                        <VisualState x:Name="NoError">
                            <Storyboard BeginTime="0">
                                <!--<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" Storyboard.TargetProperty="Width">
                                            <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                            <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>-->
                                <ObjectAnimationUsingKeyFrames BeginTime="00:00:00"
                                                                       Storyboard.TargetName="PART_InValidCellBorder"
                                                                       Storyboard.TargetProperty="(UIElement.Visibility)">
                                    <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Collapsed}" />
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="HasError">
                            <Storyboard>
                                <!--<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" Storyboard.TargetProperty="Width">
                                            <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                            <EasingDoubleKeyFrame KeyTime="0" Value="10" />
                                        </DoubleAnimationUsingKeyFrames>-->
                                <ObjectAnimationUsingKeyFrames BeginTime="00:00:00"
                                                                       Storyboard.TargetName="PART_InValidCellBorder"
                                                                       Storyboard.TargetProperty="(UIElement.Visibility)">
                                    <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}" />
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>

                    </VisualStateGroup>
                    <VisualStateGroup x:Name="BorderStates">
                        <VisualState x:Name="NormalCell" />
                        <VisualState x:Name="FrozenColumnCell"/>
                        <VisualState x:Name="FooterColumnCell">
                            <Storyboard BeginTime="0">
                                <ThicknessAnimationUsingKeyFrames BeginTime="0"
                                                                          Duration="1"
                                                                          Storyboard.TargetName="PART_GridCellBorder"
                                                                          Storyboard.TargetProperty="BorderThickness">
                                    <EasingThicknessKeyFrame KeyTime="0" Value="1,0,1,1" />
                                </ThicknessAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="BeforeFooterColumnCell">
                            <Storyboard BeginTime="0">
                                <ThicknessAnimationUsingKeyFrames BeginTime="0"
                                                                          Duration="1"
                                                                          Storyboard.TargetName="PART_GridCellBorder"
                                                                          Storyboard.TargetProperty="BorderThickness">
                                    <EasingThicknessKeyFrame KeyTime="0" Value="0,0,0,1" />
                                </ThicknessAnimationUsingKeyFrames>
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
                <Border Background="{TemplateBinding CellSelectionBrush}"
                                SnapsToDevicePixels="True"
                                Visibility="{TemplateBinding SelectionBorderVisibility}" />

                <Border x:Name="PART_GridCellBorder"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                SnapsToDevicePixels="True">
                    <ContentPresenter Margin="{TemplateBinding Padding}" Opacity="{TemplateBinding AnimationOpacity}" />
                </Border>

                <Border Margin="0,0,1,1"
                                Background="Transparent"
                                BorderBrush="{TemplateBinding CurrentCellBorderBrush}"
                                BorderThickness="{TemplateBinding CurrentCellBorderThickness}"
                                IsHitTestVisible="False"
                                SnapsToDevicePixels="True"
                                Visibility="{TemplateBinding CurrentCellBorderVisibility}" />
                <Border x:Name="PART_InValidCellBorder"
                                Width="10"
                                Height="10"
                                HorizontalAlignment="Right"
                                VerticalAlignment="Top"
                                SnapsToDevicePixels="True"
                                Visibility="Collapsed">
                    <ToolTipService.ToolTip>
                        <ToolTip Background="#FFDB000C"
                                         Placement="Right"
                                         PlacementRectangle="20,0,0,0"
                                         Tag="{TemplateBinding ErrorMessage}"
                                         Template="{StaticResource ValidationToolTipTemplate}" />
                    </ToolTipService.ToolTip>
                    <Path Data="M0.5,0.5 L12.652698,0.5 12.652698,12.068006 z"
                                  Fill="Red"
                                  SnapsToDevicePixels="True"
                                  Stretch="Fill" />
                </Border>
            </Grid>
        </ControlTemplate>
        <Style TargetType="Syncfusion:GridHeaderCellControl">
            <Setter Property="BorderThickness" Value="0.4"/>
        </Style>
        <Style TargetType="Syncfusion:GridCell">
            <Setter Property="BorderThickness" Value="0.4"/>
            <Setter Property="Template" Value="{StaticResource GridCellControlTemplate}"/>
        </Style>
    </Window.Resources>

You can download a working demo for the above customization from here.

Limitations

  1. Non editable columns will not support custom validation except GridCheckBoxColumn.
  2. CurrentCellValidating event will not triggered for GridTemplateColumn and GridUnboundColumn.

See Also

How to navigate to the error cells in datagrid via button click?

How to validate the AddNewRow value based on already existing records?

How to show the validation tooltip without hovering the red indicator in cell?

How to fire RowValidating event for GridCheckBoxColumn in SfDataGrid

How to remove the top-right corner error mark from the GridCell by pressing Esc key when validated by handling the CurrentCellValidating event?

How to change the validation error template color?

How to wire the RowValidating event after pasted the content to datagrid?