Data Validation in WinUI DataGrid

25 Apr 202224 minutes to read

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 INotifyDataErrorInfo and Data annotation attributes, can be enabled by setting SfDataGrid.DataValidationMode or GridColumn.DataValidationMode properties.

GridColumn.DataValidationMode takes priority than SfDataGrid.DataValidationMode.

  • GridValidationMode.InView - displays error icons and tips alone.
  • GridValidationMode.None - disables built-in validation support.

Built-in validation using INotifyDataErrorInfo

SfDataGrid provides support to validate the data based on INotifyDataErrorInfo.

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

public class Employee : INotifyDataErrorInfo
{
    private int _EmployeeID;


    /// <summary>
    /// Gets or sets the employee ID.
    /// </summary>
    /// <value>The employee ID.</value>
    public int EmployeeID
    {
        get
        {
            return this._EmployeeID;
        }
        set
        {
            this._EmployeeID = value;
        }
    }

    private List<string> errors = new List<string>();
    public IEnumerable<object> GetErrors(string propertyName)
    {
        if (!propertyName.Equals("EmployeeID"))
            return null;

        if (this.EmployeeID == 1003 || this.EmployeeID == 1004)
            errors.Add("Delivery not available for " + this.EmployeeID);
        return errors;
    }

    [Display(AutoGenerateField = false)]
    public bool HasErrors
    {
        get
        {
            if (this.EmployeeID == 1003 || this.EmployeeID == 1004)
                return true;
            return false;
        }
    }
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}

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

<dataGrid:SfDataGrid  x:Name="sfDataGrid"
                        DataValidationMode="InView"
                        AutoGenerateColumns="True"
                        ItemsSource="{Binding Employees}"/>
this.sfDataGrid.DataValidationMode = GridValidationMode.InView;

Data Validation in WinUI DataGrid using INotifyDataErrorInfo

Built-in validation using Data Annotation

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

Using different annotations

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

private int _EmployeeID;

/// <summary>
/// Gets or sets the employee ID.
/// </summary>
/// <value>The employee ID.</value>
[Range(1002, 1008, ErrorMessage = "EmployeeID between 1002 and 1008 alone processed")]
public int EmployeeID
{
    get
    {
        return this._EmployeeID;
    }
    set
    {
        this._EmployeeID = value;
    }
}

 private double _Salary;

/// <summary>
/// Gets or sets the Salary.
/// </summary>
/// <value>The Salary.</value>
[Range(typeof(double), "1000", "5000", ErrorMessage = "The “Salary” field can range from 2000 to 5000.")]
public double Salary
{
    get
    {
        return this._Salary;
    }
    set
    {
        this._Salary = value;
    }
}

Data Validation in WinUI DataGrid using Data Annotations

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

private string _Name;

/// <summary>
/// Gets or sets the last name.
/// </summary>
/// <value>The last name.</value>
[StringLength(17)]
public string Name
{
    get
    {
        return this._Name;
    }
    set
    {
        this._Name = value;
    }
}

private string _Title;

/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
[Required]
public string Title
{
    get
    {
        return this._Title;
    }
    set
    {
        this._Title = value;
    }
}

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

private string _Title;
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
[RegularExpressionAttribute(@"^[a-zA-Z]{1,40}$", ErrorMessage = "Numbers and special characters not allowed")]

public string Title
{
    get
    {
        return this._Title;
    }
    set
    {
        this._Title = 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.

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

this.sfDataGrid.CurrentCellValidating += SfDataGrid_CurrentCellValidating; 

private void SfDataGrid_CurrentCellValidating(object sender, CurrentCellValidatingEventArgs e)
{
    if (e.NewValue.ToString().Equals("1004"))
    {
        e.IsValid = false;
        e.ErrorMessage = "EmployeeID 1004 cannot be passed";
    }
}

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

this.sfDataGrid.CurrentCellValidated += SfDataGrid_CurrentCellValidated;

private void SfDataGrid_CurrentCellValidated(object sender, CurrentCellValidatedEventArgs e)
{
   
}

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.

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

this.sfDataGrid.RowValidating += SfDataGrid_RowValidating;

private void SfDataGrid_RowValidating(object sender, RowValidatingEventArgs e)
{
    var data = e.RowData.GetType().GetProperty("Name").GetValue(e.RowData);

    if (data.ToString().Equals("Marvin Allen"))
    {
        e.IsValid = false;
        e.ErrorMessages.Add("Name", "Marvin Allen cannot be passed");
    }
}

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

this.sfDataGrid.RowValidated += SfDataGrid_RowValidated;

private void SfDataGrid_RowValidated(object sender, RowValidatedEventArgs e)
{
           
}

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. And need to add the DataGrid ThemeDictionary in ResourcesDictionary.

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ms-appx:///Syncfusion.Core.WinUI/Themes/Common.xaml" />
        <ResourceDictionary Source="ms-appx:///Syncfusion.Grid.WinUI/Themes/themeresources.xaml" />
        <!--Other merged dictionaries here--> 
    </ResourceDictionary.MergedDictionaries>    
</ResourceDictionary>

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

		<Border Margin="4,4,-4,-4"
			Background="Transparent"
			CornerRadius="5" />
		<Border Margin="3,3,-3,-3"
			Background="Transparent"
			CornerRadius="4" />
		<Border Margin="2,2,-2,-2"
			Background="Transparent"
			CornerRadius="3" />
		<Border Margin="1,1,-1,-1"
			Background="Transparent"
			CornerRadius="2" />

		<Border Background="{ThemeResource SystemFillColorCritical}" CornerRadius="2" />
		<Border CornerRadius="2" BorderBrush="{ThemeResource SystemFillColorCritical}">
			<TextBlock MaxWidth="250"
			   Margin="8,4,8,4"
			   FontFamily="{ThemeResource SyncfusionDataGridFontFamily}"
			   FontSize="{ThemeResource SyncfusionBodyFontSize}"
			   FontWeight="{ThemeResource SyncfusionDataGridFontWeight}"
			   Foreground="{ThemeResource SyncfusionDataGridCellValidationToolTipErrorForeground}"
			   Text="{TemplateBinding Tag}"
			   TextWrapping="Wrap"
			   HighContrastAdjustment="None"
			   UseLayoutRounding="false" />
		</Border>
    </Grid>
</ControlTemplate>

<Style TargetType="dataGrid:GridCell">
	<Setter Property="Background" Value="Transparent" />
	<Setter Property="BorderBrush" Value="{ThemeResource SyncfusionDataGridLineStroke}" />
	<Setter Property="FontFamily" Value="{ThemeResource SyncfusionDataGridFontFamily}"/>
	<Setter Property="FontSize" Value="{ThemeResource SyncfusionBodyFontSize}"/>
	<Setter Property="FontWeight" Value="{ThemeResource SyncfusionDataGridFontWeight}"/>
	<Setter Property="BorderThickness" Value="0,0,1,1" />
	<Setter Property="Padding" Value="0,0,0,0" />
	<Setter Property="VerticalContentAlignment" Value="Center" />
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="dataGrid:GridCell">
				<Grid>
					<VisualStateManager.VisualStateGroups>
						<VisualStateGroup x:Name="IndicationStates">
							<VisualState x:Name="NoError">
							</VisualState>
							<VisualState x:Name="HasError">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0">
											<DiscreteObjectKeyFrame.Value>
												<Visibility>Visible</Visibility>
											</DiscreteObjectKeyFrame.Value>
										</DiscreteObjectKeyFrame>
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
						</VisualStateGroup>
						<VisualStateGroup x:Name="BorderStates">
							<VisualState x:Name="NormalCell" />
							<VisualState x:Name="FrozenColumnCell">
								<Storyboard BeginTime="0">
									<ObjectAnimationUsingKeyFrames BeginTime="0"
														   Duration="1"
														   Storyboard.TargetName="PART_FrozenCellBorder"
														   Storyboard.TargetProperty="BorderThickness">
										<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,1,0" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="FooterColumnCell">
								<Storyboard BeginTime="0">
									<ObjectAnimationUsingKeyFrames BeginTime="0"
														   Duration="1"
														   Storyboard.TargetName="PART_FooterCellBorder"
														   Storyboard.TargetProperty="BorderThickness">
										<DiscreteObjectKeyFrame KeyTime="0" Value="1,0,0,0" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="BeforeFooterColumnCell">
								<Storyboard BeginTime="0">
									<ObjectAnimationUsingKeyFrames BeginTime="0"
														   Duration="1"
														   Storyboard.TargetName="PART_GridCellBorder"
														   Storyboard.TargetProperty="Margin">
										<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,-1,0" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="CurrentCell">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_CurrentCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="SelectedCell">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_SelectionCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="SelectedCurrentCell">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_SelectionCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_CurrentCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
						</VisualStateGroup>
					</VisualStateManager.VisualStateGroups>
					<Border x:Name="PART_SelectionCellBorder" Background="{TemplateBinding SelectionBackground}" Visibility="Collapsed" />
					<Border x:Name="PART_FooterCellBorder"
						Background="{TemplateBinding Background}"
						BorderBrush="{TemplateBinding BorderBrush}"/>
					<Border x:Name="PART_FrozenCellBorder"
						Background="{TemplateBinding Background}"
						BorderBrush="{TemplateBinding BorderBrush}"/>
					<Border x:Name="PART_GridCellBorder"
						Background="{TemplateBinding Background}"
						BorderBrush="{TemplateBinding BorderBrush}"
						BorderThickness="{TemplateBinding GridCellBorderThickness}">

						<ContentPresenter Margin="{TemplateBinding Padding}"
								  FontFamily="{TemplateBinding FontFamily}"
								  FontWeight="{TemplateBinding FontWeight}"
								  FontSize="{TemplateBinding FontSize}" />

					</Border>
					<Border x:Name="PART_CurrentCellBorder" 
						Margin="0,0,1,1"
						Background="Transparent"
						BorderBrush="{TemplateBinding CurrentCellBorderBrush}"
						BorderThickness="{TemplateBinding CurrentCellBorderThickness}"
						IsHitTestVisible="False"
						Visibility="Collapsed"/>
						<Border x:Name="PART_InValidCellBorder"
						Width="10"
						Height="10"
						HorizontalAlignment="Right"
						VerticalAlignment="Top"
						Visibility="Collapsed">

						<ToolTipService.ToolTip>
							<ToolTip Placement="Right"
								 Tag="{TemplateBinding ErrorMessage}"
								 Template="{StaticResource DataGridCellValidationToolTipTemplate}" />
						</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="{ThemeResource SystemFillColorCritical}"
					          Stretch="Fill" />
					</Border>
				</Grid>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

WinUI 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.

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ms-appx:///Syncfusion.Core.WinUI/Themes/Common.xaml" />
        <ResourceDictionary Source="ms-appx:///Syncfusion.Grid.WinUI/Themes/themeresources.xaml" />
        <!--Other merged dictionaries here--> 
    </ResourceDictionary.MergedDictionaries>    
</ResourceDictionary>

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

		<Border Margin="4,4,-4,-4"
			Background="Transparent"
			CornerRadius="5" />
		<Border Margin="3,3,-3,-3"
			Background="Transparent"
			CornerRadius="4" />
		<Border Margin="2,2,-2,-2"
			Background="Transparent"
			CornerRadius="3" />
		<Border Margin="1,1,-1,-1"
			Background="Transparent"
			CornerRadius="2" />

		<Border Background="{ThemeResource SystemFillColorCritical}" CornerRadius="2" />
		<Border CornerRadius="2" BorderBrush="{ThemeResource SystemFillColorCritical}">
			<TextBlock MaxWidth="250"
			   Margin="8,4,8,4"
			   FontFamily="{ThemeResource SyncfusionDataGridFontFamily}"
			   FontSize="{ThemeResource SyncfusionBodyFontSize}"
			   FontWeight="{ThemeResource SyncfusionDataGridFontWeight}"
			   Foreground="{ThemeResource SyncfusionDataGridCellValidationToolTipErrorForeground}"
			   Text="{TemplateBinding Tag}"
			   TextWrapping="Wrap"
			   HighContrastAdjustment="None"
			   UseLayoutRounding="false" />
		</Border>
    </Grid>
</ControlTemplate>

<Style TargetType="dataGrid:GridCell">
	<Setter Property="Background" Value="Transparent" />
	<Setter Property="BorderBrush" Value="{ThemeResource SyncfusionDataGridLineStroke}" />
	<Setter Property="FontFamily" Value="{ThemeResource SyncfusionDataGridFontFamily}"/>
	<Setter Property="FontSize" Value="{ThemeResource SyncfusionBodyFontSize}"/>
	<Setter Property="FontWeight" Value="{ThemeResource SyncfusionDataGridFontWeight}"/>
	<Setter Property="BorderThickness" Value="0,0,1,1" />
	<Setter Property="Padding" Value="0,0,0,0" />
	<Setter Property="VerticalContentAlignment" Value="Center" />
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="dataGrid:GridCell">
				<Grid>
					<VisualStateManager.VisualStateGroups>
						<VisualStateGroup x:Name="IndicationStates">
							<VisualState x:Name="NoError">
							</VisualState>
							<VisualState x:Name="HasError">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_InValidCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0">
											<DiscreteObjectKeyFrame.Value>
												<Visibility>Visible</Visibility>
											</DiscreteObjectKeyFrame.Value>
										</DiscreteObjectKeyFrame>
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
						</VisualStateGroup>
						<VisualStateGroup x:Name="BorderStates">
							<VisualState x:Name="NormalCell" />
							<VisualState x:Name="FrozenColumnCell">
								<Storyboard BeginTime="0">
									<ObjectAnimationUsingKeyFrames BeginTime="0"
														   Duration="1"
														   Storyboard.TargetName="PART_FrozenCellBorder"
														   Storyboard.TargetProperty="BorderThickness">
										<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,1,0" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="FooterColumnCell">
								<Storyboard BeginTime="0">
									<ObjectAnimationUsingKeyFrames BeginTime="0"
														   Duration="1"
														   Storyboard.TargetName="PART_FooterCellBorder"
														   Storyboard.TargetProperty="BorderThickness">
										<DiscreteObjectKeyFrame KeyTime="0" Value="1,0,0,0" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="BeforeFooterColumnCell">
								<Storyboard BeginTime="0">
									<ObjectAnimationUsingKeyFrames BeginTime="0"
														   Duration="1"
														   Storyboard.TargetName="PART_GridCellBorder"
														   Storyboard.TargetProperty="Margin">
										<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,-1,0" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="CurrentCell">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_CurrentCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="SelectedCell">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_SelectionCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
							<VisualState x:Name="SelectedCurrentCell">
								<Storyboard>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_SelectionCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
									<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_CurrentCellBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
										<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
									</ObjectAnimationUsingKeyFrames>
								</Storyboard>
							</VisualState>
						</VisualStateGroup>
					</VisualStateManager.VisualStateGroups>
					<Border x:Name="PART_SelectionCellBorder" Background="{TemplateBinding SelectionBackground}" Visibility="Collapsed" />
					<Border x:Name="PART_FooterCellBorder"
						Background="{TemplateBinding Background}"
						BorderBrush="{TemplateBinding BorderBrush}"/>
					<Border x:Name="PART_FrozenCellBorder"
						Background="{TemplateBinding Background}"
						BorderBrush="{TemplateBinding BorderBrush}"/>
					<Border x:Name="PART_GridCellBorder"
						Background="{TemplateBinding Background}"
						BorderBrush="{TemplateBinding BorderBrush}"
						BorderThickness="{TemplateBinding GridCellBorderThickness}">

						<ContentPresenter Margin="{TemplateBinding Padding}"
								  FontFamily="{TemplateBinding FontFamily}"
								  FontWeight="{TemplateBinding FontWeight}"
								  FontSize="{TemplateBinding FontSize}" />

					</Border>
					<Border x:Name="PART_CurrentCellBorder" 
						Margin="0,0,1,1"
						Background="Transparent"
						BorderBrush="{TemplateBinding CurrentCellBorderBrush}"
						BorderThickness="{TemplateBinding CurrentCellBorderThickness}"
						IsHitTestVisible="False"
						Visibility="Collapsed"/>
						<Border x:Name="PART_InValidCellBorder"
						Width="10"
						Height="10"
						HorizontalAlignment="Right"
						VerticalAlignment="Top"
						Visibility="Collapsed">

						<ToolTipService.ToolTip>
							<ToolTip Placement="Right"
								 Tag="{TemplateBinding ErrorMessage}"
								 Template="{StaticResource DataGridCellValidationToolTipTemplate}" />
						</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="Orange"
					          Stretch="Fill" />
					</Border>
				</Grid>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

Data Validation Error Icon with Custom Color in WinUI 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="DataGridCellValidationToolTipTemplate">
    <Grid x:Name="PART_ToolTipPresenter"
          Margin="5,0"
          Opacity="0"
          RenderTransformOrigin="0,0">
		<Grid.RenderTransform>
			<TranslateTransform x:Name="Transform" X="-25" />
		</Grid.RenderTransform>
		<VisualStateManager.VisualStateGroups>
			<VisualStateGroup x:Name="OpenStates">
				<VisualStateGroup.Transitions>
					<VisualTransition GeneratedDuration="0" />
					<VisualTransition GeneratedDuration="0:0:0.2" To="Opened">
						<Storyboard>
							<DoubleAnimation Duration="0:0:0.2"
									 Storyboard.TargetName="Transform"
									 Storyboard.TargetProperty="X"
									 To="0">
								<DoubleAnimation.EasingFunction>
									<BackEase Amplitude=".3" EasingMode="EaseOut" />
								</DoubleAnimation.EasingFunction>
							</DoubleAnimation>
							<DoubleAnimation Duration="0:0:0.2"
									 Storyboard.TargetName="PART_ToolTipPresenter"
									 Storyboard.TargetProperty="Opacity"
									 To="1" />
						</Storyboard>
					</VisualTransition>
				</VisualStateGroup.Transitions>
				<VisualState x:Name="Closed">
					<Storyboard>
						<DoubleAnimation Duration="0"
								 Storyboard.TargetName="PART_ToolTipPresenter"
								 Storyboard.TargetProperty="Opacity"
								 To="0" />
					</Storyboard>
				</VisualState>
				<VisualState x:Name="Opened">
					<Storyboard>
						<DoubleAnimation Duration="0"
								 Storyboard.TargetName="Transform"
								 Storyboard.TargetProperty="X"
								 To="0" />
						<DoubleAnimation Duration="0"
								 Storyboard.TargetName="PART_ToolTipPresenter"
								 Storyboard.TargetProperty="Opacity"
								 To="1" />
					</Storyboard>
				</VisualState>
			</VisualStateGroup>
		</VisualStateManager.VisualStateGroups>

		<Border Margin="4,4,-4,-4"
			Background="Transparent"
			CornerRadius="5" />
		<Border Margin="3,3,-3,-3"
			Background="Transparent"
			CornerRadius="4" />
		<Border Margin="2,2,-2,-2"
			Background="Transparent"
			CornerRadius="3" />
		<Border Margin="1,1,-1,-1"
			Background="Transparent"
			CornerRadius="2" />

		<Border Background="{ThemeResource SystemFillColorCritical}" CornerRadius="2" />
		<Border CornerRadius="2" Background="Orange" BorderBrush="{ThemeResource SystemFillColorCritical}">
			<TextBlock MaxWidth="250"
			   Margin="8,4,8,4"
			   FontFamily="{ThemeResource SyncfusionDataGridFontFamily}"
			   FontSize="{ThemeResource SyncfusionBodyFontSize}"
			   FontWeight="{ThemeResource SyncfusionDataGridFontWeight}"
			   Foreground="Black"
			   Text="{TemplateBinding Tag}"
			   TextWrapping="Wrap"
			   HighContrastAdjustment="None"
			   UseLayoutRounding="false" />
		</Border>
    </Grid>
</ControlTemplate>

WinUI DataGrid - Error Tip Foreground and Background Customization

Showing error details in RowHeader

SfDataGrid support to show the error icon in GridRowHeaderCell based on INotifyDataErrorInfo.HasErrors property.

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.EmployeeID == 1003 || this.EmployeeID == 1004)
            return true;
        return false;
    }
}

WinUI 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 INotifyDataErrorInfo or Data Annotation attributes by setting DataValidationMode property of GridViewDefinition.DataGrid.

<dataGrid:SfDataGrid  x:Name="sfDataGrid"
                        AutoGenerateColumns="True"
                        ItemsSource="{Binding OrdersDetails}">
            <dataGrid:SfDataGrid.DetailsViewDefinition>
                <dataGrid:GridViewDefinition RelationalColumn="OrderDetails">
                    <dataGrid:GridViewDefinition.DataGrid>
                        <dataGrid:SfDataGrid  x:Name="firstLevelDetailsViewGrid"  
                                                AutoGenerateColumns="True"   
                                                DataValidationMode="InView"/>
                    </dataGrid:GridViewDefinition.DataGrid>
                </dataGrid:GridViewDefinition>
            </dataGrid:SfDataGrid.DetailsViewDefinition>
</dataGrid:SfDataGrid>

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

sfDataGrid.AutoGenerateRelations = true;

sfDataGrid.AutoGeneratingRelations += SfDataGrid_AutoGeneratingRelations;

private void SfDataGrid_AutoGeneratingRelations(object sender, AutoGeneratingRelationsArgs e)
{
    e.GridViewDefinition.DataGrid.DataValidationMode = GridValidationMode.InView;
}

Data Validation with Master-DetailsView in WinUI 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 GridViewDefinition.DataGrid when the cell is edited. CurrentCellValidating event occurs when the edited cells tries to commit the data or lose the focus.

<dataGrid:SfDataGrid  x:Name="sfDataGrid"
                        AutoGenerateColumns="True"
                        DataValidationMode="InView"
                        NavigationMode="Cell"
                        ItemsSource="{Binding OrdersDetails}">
            <dataGrid:SfDataGrid.DetailsViewDefinition>
                <dataGrid:GridViewDefinition RelationalColumn="OrderDetails">
                    <dataGrid:GridViewDefinition.DataGrid>
                        <dataGrid:SfDataGrid  x:Name="firstLevelDetailsViewGrid" 
                                                AutoGenerateColumns="True"       CurrentCellValidating="FirstLevelDetailsViewGrid_CurrentCellValidating">
                        </dataGrid:SfDataGrid>
                    </dataGrid:GridViewDefinition.DataGrid>
                </dataGrid:GridViewDefinition>
            </dataGrid:SfDataGrid.DetailsViewDefinition>
</dataGrid:SfDataGrid>


this.firstLevelDetailsViewGrid.CurrentCellValidating += FirstLevelDetailsViewGrid_CurrentCellValidating;

private void FirstLevelDetailsViewGrid_CurrentCellValidating(object sender, CurrentCellValidatingEventArgs e)
{
    if (e.NewValue.ToString().Equals("FRANS"))
    {
        e.IsValid = false;
        e.ErrorMessage = "Order not delivered for the ID FRANS";
    }
}

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

<dataGrid:SfDataGrid  x:Name="sfDataGrid"
                        AutoGenerateColumns="True"
                        DataValidationMode="InView"
                        NavigationMode="Cell"
                        ItemsSource="{Binding OrdersDetails}"
                        AllowEditing="True">
    <dataGrid:SfDataGrid.DetailsViewDefinition>
        <dataGrid:GridViewDefinition RelationalColumn="OrderDetails">
            <dataGrid:GridViewDefinition.DataGrid>
                <dataGrid:SfDataGrid  x:Name="firstLevelDetailsViewGrid" 
                                        AutoGenerateColumns="True" 
                                        CurrentCellValidated="FirstLevelDetailsViewGrid_CurrentCellValidated" 
                                        AllowEditing="True" 
                                        DataValidationMode="InView">
                 </dataGrid:SfDataGrid>
             </dataGrid:GridViewDefinition.DataGrid>
         </dataGrid:GridViewDefinition>
     </dataGrid:SfDataGrid.DetailsViewDefinition>
 </dataGrid:SfDataGrid>


this.firstLevelDetailsViewGrid.CurrentCellValidated += FirstLevelDetailsViewGrid_CurrentCellValidated;

private void FirstLevelDetailsViewGrid_CurrentCellValidated(object sender, CurrentCellValidatedEventArgs e)
{

}

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

sfDataGrid.AutoGenerateRelations = true;

sfDataGrid.AutoGeneratingRelations += SfDataGrid_AutoGeneratingRelations;

private void SfDataGrid_AutoGeneratingRelations(object sender, AutoGeneratingRelationsArgs e)
{
    e.GridViewDefinition.DataGrid.CurrentCellValidating += FirstLevelDetailsViewGrid_CurrentCellValidating;
    e.GridViewDefinition.DataGrid.CurrentCellValidated += FirstLevelDetailsViewGrid_CurrentCellValidated;
}

Row Validation

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

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

<dataGrid:SfDataGrid  x:Name="sfDataGrid"
                        AutoGenerateColumns="True"
                        DataValidationMode="InView"
                        ItemsSource="{Binding OrdersDetails}">
            <dataGrid:SfDataGrid.DetailsViewDefinition>
                <dataGrid:GridViewDefinition RelationalColumn="OrderDetails">
                    <dataGrid:GridViewDefinition.DataGrid>
                        <dataGrid:SfDataGrid x:Name="firstLevelDetailsViewGrid" 
                                               AutoGenerateColumns="True" 
                                               AllowEditing="True" 
                                               DataValidationMode="InView"
                                               RowValidating="FirstLevelDetailsViewGrid_RowValidating">
                        </dataGrid:SfDataGrid>
                    </dataGrid:GridViewDefinition.DataGrid>
                </dataGrid:GridViewDefinition>
            </dataGrid:SfDataGrid.DetailsViewDefinition>
</dataGrid:SfDataGrid>


this.firstLevelDetailsViewGrid.RowValidating += FirstLevelDetailsViewGrid_RowValidating;

private void FirstLevelDetailsViewGrid_RowValidating(object sender, RowValidatingEventArgs e)
{
    var data = e.RowData.GetType().GetProperty("CustomerID").GetValue(e.RowData);
    if (data.ToString().Equals("FRANS"))
    {
        e.IsValid = false;
        e.ErrorMessages.Add("CustomerID", "Order not delivered for the ID FRANS");
    }
}

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

<dataGrid:SfDataGrid  x:Name="sfDataGrid"
                        AutoGenerateColumns="True"
                        ItemsSource="{Binding OrdersDetails}">
            <dataGrid:SfDataGrid.DetailsViewDefinition>
                <dataGrid:GridViewDefinition RelationalColumn="OrderDetails">
                    <dataGrid:GridViewDefinition.DataGrid>
                        <dataGrid:SfDataGrid x:Name="firstLevelDetailsViewGrid" 
                                               AutoGenerateColumns="True" 
                                               AllowEditing="True" 
                                               DataValidationMode="InView"
                                               RowValidated="FirstLevelDetailsViewGrid_RowValidated">
                        </dataGrid:SfDataGrid>
                    </dataGrid:GridViewDefinition.DataGrid>
                </dataGrid:GridViewDefinition>
            </dataGrid:SfDataGrid.DetailsViewDefinition>
</dataGrid:SfDataGrid>


this.firstLevelDetailsViewGrid.RowValidated += FirstLevelDetailsViewGrid_RowValidated;

private void FirstLevelDetailsViewGrid_RowValidated(object sender, RowValidatedEventArgs e)
{

}

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

sfDataGrid.AutoGenerateRelations = true;

sfDataGrid.AutoGeneratingRelations += SfDataGrid_AutoGeneratingRelations;

private void SfDataGrid_AutoGeneratingRelations(object sender, AutoGeneratingRelationsArgs e)
{
    e.GridViewDefinition.DataGrid.RowValidating += FirstLevelDetailsViewGrid_RowValidating;
    e.GridViewDefinition.DataGrid.RowValidated += FirstLevelDetailsViewGrid_RowValidated;
}

Limitations

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