Data validation is a key part in WPF.
Validation is used to alert the user that the data he entered is illegal.
In this post we will see how to Validate user input using MVVM binding.
I have created 2 different templates for input validation :
Click WPFInputValidation Link To Download full source.
Example 1 :
To impelment validation we need to use IDataErrorInfo interface.
IDataErrorInfo interface provides the functionality to offer custom error information that a user interface can bind to.
It has 2 Properties :
- Error : Gets an error message indicating what is wrong with this object.
- string this[string columnName] Indexer : it will return the error message for the property. The default is an empty string ("")
Let's go through step by step to understand validation with mvvm binding and styling the validation template.
Step 1 : First change ErrorToolTip style by customizing the Template.
<ControlTemplate x:Key="ErrorToolTipTemplate_1"> <ControlTemplate.Resources> <Style x:Key="textblockErrorTooltip" TargetType="TextBlock"> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="Foreground" Value="White" /> <Setter Property="Margin" Value="10 0 10 0" /> </Style> </ControlTemplate.Resources> <DockPanel LastChildFill="true"> <Border Height="Auto" Margin="5,0,0,0" Background="#DC000C" CornerRadius="3" DockPanel.Dock="right"> <TextBlock Style="{StaticResource textblockErrorTooltip}" Text="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" /> </Border> <AdornedElementPlaceholder Name="customAdorner"> <Border BorderBrush="#DC000C" BorderThickness="1.3" /> </AdornedElementPlaceholder> </DockPanel> </ControlTemplate>
ErrorTemplate uses adorner layer. which is drawing layer, using adorner layer you can add visual appearance to indicate an error without replacing controltemplate.
AdornedElementPlaceholder is part of the Validation feature of data binding. it specify where a decorated control is placed relative to other elements in the ControlTemplate.
Step 2 : Create TextBox style and set Validation ErrorTemplate.
<Style TargetType="TextBox"> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="VerticalAlignment" Value="Top" /> <Setter Property="Width" Value="150" /> <Setter Property="Height" Value="30" /> <Setter Property="Validation.ErrorTemplate" Value="{DynamicResource ErrorToolTipTemplate_1}" /> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" /> </Trigger> </Style.Triggers> </Style>
public class InputValidationViewModel : ViewModelBase { public InputValidationViewModel() { } private string employeeName; public string EmployeeName { get { return employeeName; } set { employeeName = value; RaisePropertyChanged("EmployeeName"); } } private string email; public string Email { get { return email; } set {email = value; RaisePropertyChanged("Email"); } } private long phoneNumber; public long PhoneNumber { get { return phoneNumber; } set { phoneNumber = value; RaisePropertyChanged("PhoneNumber"); } } private bool IsValidEmailAddress { get { return emailRegex.IsMatch(Email); } } }
as shown in above code, ViewModel created and added some propeties that need to bind in UserControl.
public class InputValidationViewModel : ViewModelBase, IDataErrorInfo { private Regex emailRegex = new Regex(@"^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$"); public InputValidationViewModel() { } private string error = string.Empty; public string Error { get { return error; } } public string this[string columnName] { get { error = string.Empty; if (columnName == "EmployeeName" && string.IsNullOrWhiteSpace(EmployeeName)) { error = "Employee name is required!"; } else if (columnName == "PhoneNumber" && PhoneNumber == 0) { error = "Phone number is required!"; } else if (columnName == "PhoneNumber" && PhoneNumber.ToString().Length > 10) { error = "Phone number must have less than or equal to 10 digits!"; } else if (columnName == "Email" && string.IsNullOrWhiteSpace(Email)) { error = "Email address is required!"; } else if (columnName == "Email" && !IsValidEmailAddress) { error = "Please enter valid email address!"; } return error; } } }
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding EmployeeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
As shown in above image, tooltip style is changed, ! mark with red circle surrounded image is added on the errortemplate.
small triangle added on template, to show top right corner of the control if any data error exists.
below are the template change compare to previous example template :
<Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1.2" CornerRadius="1" ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"> <Grid Width="12" Height="12" Margin="1,-4,-4,0" HorizontalAlignment="Right" VerticalAlignment="Top" Background="Transparent"> <Path Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" /> <Path Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" /> </Grid> </Border> <Border Grid.Column="0" Width="15" Height="15" Margin="0 0 3 0" HorizontalAlignment="Right" VerticalAlignment="Center" Background="Red" CornerRadius="10" ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"> <TextBlock HorizontalAlignment="center" VerticalAlignment="center" FontWeight="Bold" Foreground="white" Text="!" /> </Border>
as shown in above code,