Liens
ValidatesOnExceptions
Le Binding catch les exceptions, il est donc possible de lancer une exception si la valeur n'est pas valide.
|
<TextBox Text="{Binding InputValue, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}">
|
|
private string _inputValue;
public string InputValue
{
get => _inputValue;
set
{
// le test est réalisé avant l'affectation de la valeur
// ce qui empeche l'affectation de la valeur si celle-ci n'est pas validée
if(value.Contains("x"))
{
throw new ArgumentException("Message d'ereeur.");
}
_inputValue= value;
}
}
|
Classe ValidationRule
La validation est réalisée avant l'affectation de la valeur, celle-ci n'est setée que si elle est valide.
Desavantage: la validation se fait uniquement sur la valeur, le context n'est pas accessible.
Les paramètres ne peuvent être bindés car ce ne sont pas des dependancy properties, leurs valeurs sont donc statiques.
|
public class MyValidationRule : ValidationRule
{
public string ForbiddenValue { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (ForbiddenValue == null)
return ValidationResult.ValidResult;
if (value is string input && !input.Contains("x"))
{
return ValidationResult.ValidResult;
}
return new ValidationResult(false, "Error message.");
}
}
|
|
<UserControl xmlns:local="clr-namespace:Main.Views">
<TextBox>
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<local:MyValidationRule ForbiddenValue="x" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
|
ExceptionValidationRule
|
<TextBox>
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
|
IDataErrorInfo
Désaventage: la valeur est setée avant la validation.
|
<TextBox Text="{Binding InputValue, ValidatesOnDataErrors=True}" />
|
|
public class MyViewModel : ViewModelBase, IDataErrorInfo
{
// erreur globale liée au VM
public string Error => string.Empty;
// déclenché lors de l'appel du set sur une propriété
public string this[string propertyName] => GetErrorForProperty(propertyName);
private string GetErrorForProperty(string propertyName)
{
switch (propertyName)
{
case nameof(InputValue):
{
if (InputValue.Contains("x"))
{
// si le retour est différent de null ou vide
// le programme considère qu'il y a une erreur.
return "Error message.";
}
return string.Empty;
}
default:
return string.Empty;
}
|
INotifyDataErrorInfo
Même concept qu'IDataErrorInfo avec une validation asynchrone.
Permet aussi de renvoyer plusieurs erreurs pour une seule propriété.
|
<!-- ValidatesOnNotifyDataErrors est par défaut à True -->
<TextBox Text="{Binding InputValue, ValidatesOnNotifyDataErrors=True}" />
|
|
public class MyViewModel : ViewModelBase, INotifyDataErrorInfo
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
// use lock because of multi-thread async calls
// or a concurrent collection instead of a Dictionary
lock (_propertyErrors)
{
if (_propertyErrors.ContainsKey(propertyName))
return _propertyErrors[propertyName];
}
return null;
}
public bool HasErrors => _propertyErrors.Values.Any(errors => errors != null);
private readonly Dictionary<string, IList<string>> _propertyErrors = new Dictionary<string, IList<string>>();
/* propriété bindée avec sa validation async */
private string _inputValue;
public string InputValue
{
get => _inputValue;
set
{
SetProperty(ref _inputValue, value);
ValidateAsync(InputValue).ContinueWith((errorTasks) =>
{
lock (_propertyErrors)
{
_propertyErrors[nameof(InputValue)] = errorTasks.Result;
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(InputValue)));
}
});
}
}
private async Task<IList<string>> ValidateAsync(string inputValue)
{
await Task.Run(() =>
{
Thread.Sleep(4000);
});
return inputValue.Contains("y") ? new List<string> { "Error message." } : null;
}
|
Event
Lève un évenement en cas d'erreur de validation.
|
<TextBox Text="{Binding InputValue, NotifyOnValidationError=True}"
Validation.Error="Validation_OnError" />
<!-- l'evt est de type bubble, Validation.Error peut ainsi être placé dans un parent de l'abre visuel -->
|
|
private void Validation_OnError(object sender, ValidationErrorEventArgs e)
{
Debug.WriteLine(e.Error.ErrorContent);
}
|
Style
|
<Style x:Key="ErrorStyle" TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
|
ErrorTemplate
Change la manière dont est signalé une erreur.
|
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right"
Margin="2,0"
ToolTip="Invalid data"
Width="10"
Height="10">
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Red" Offset="1" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="4,4,15,4" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
|
DataAnnotation
Ajouter la référence System.ComponentModel.DataAnnotations
ValidationException
MyViewModel.cs
|
public class MyViewModel : BindableBase
{
private string _inputValue;
[Required(AllowEmptyStrings = false, ErrorMessage = "InputValue is required")]
public string InputValue
{
get => _inputValue;
set
{
ValidateProperty(nameof(InputValue), value);
SetProperty(ref _inputValue, value);
}
}
// à placer dans une classe abstraite dont tous les VM pourront hériter
private void ValidateProperty(string propertyName, object value)
{
var context = new ValidationContext(this);
context.MemberName = propertyName;
// lève une ValidationException en cas d'erreur de validation
Validator.ValidateProperty(value, context);
}
|
IDataErrorInfo
MyViewModel.cs
|
public class MyViewModel : BindableBase, IDataErrorInfo
{
private string _inputValue;
[Required(AllowEmptyStrings = false, ErrorMessage = "InputValue is required")]
public string InputValue
{
get => _inputValue;
set => SetProperty(ref _inputValue, value);
}
public string Error => string.Empty;
public string this[string propertyName] => GetErrorForProperty(propertyName);
private string GetErrorForProperty(string propertyName)
{
var validationResults = ValidateProperty(propertyName, GetType().GetProperty(propertyName)?.GetValue(this));
return validationResults.FirstOrDefault()?.ErrorMessage;
}
// à placer dans une classe abstraite dont tous les VM pourront hériter
private IList<ValidationResult> ValidateProperty(string propertyName, object value)
{
var context = new ValidationContext(this);
context.MemberName = propertyName;
var validationResults = new List<ValidationResult>();
Validator.TryValidateProperty(value, context, validationResults);
return validationResults;
}
|
INotifyDataErrorInfo
MyViewModel.cs
|
public class MyViewModel : BindableBase, INotifyDataErrorInfo
{
private string _inputValue;
[Required(AllowEmptyStrings = false, ErrorMessage = "InputValue is required")]
public string InputValue
{
get => _inputValue;
set
{
ValidateProperty(nameof(InputValue), value);
SetProperty(ref _inputValue, value);
}
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
// use lock because of multi-thread async calls, or a concurrent collection instead of a Dictionary
lock (_propertyErrors)
{
if (_propertyErrors.ContainsKey(propertyName))
return _propertyErrors[propertyName];
}
return null;
}
public bool HasErrors => _propertyErrors.Values.Any(errors => errors != null);
private readonly Dictionary<string, IList<string>> _propertyErrors = new Dictionary<string, IList<string>>();
// à placer dans une classe abstraite dont tous les VM pourront hériter
private void ValidateProperty(string propertyName, object value)
{
var context = new ValidationContext(this);
context.MemberName = propertyName;
var validationResults = new List<ValidationResult>();
Validator.TryValidateProperty(value, context, validationResults);
lock (_propertyErrors)
{
_propertyErrors[propertyName] = validationResults.Select(vr => vr.ErrorMessage).ToList();
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
|
Validation d'une fenêtre avec un bouton
|
<TextBox Text="{Binding Path=..., UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true, NotifyOnValidationError=true}"
Validation.Error="Validation_Error" />
<Button Command="{x:Static views:ReportSaver.SaveCmd}">OK</Button>
|
|
public static RoutedCommand SaveCmd = new RoutedCommand();
private void ExecutedSaveCmd(object sender, ExecutedRoutedEventArgs e)
{
this.DialogResult = true;
this.Close();
}
private void CanExecuteSaveCmd(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = _noOfErrorsOnScreen == 0;
e.Handled = true;
}
private int _noOfErrorsOnScreen = 0;
private void Validation_Error(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
_noOfErrorsOnScreen++;
else
_noOfErrorsOnScreen--;
}
|
Permet d'avoir le visuel de validation dans un control parent.