WPF 的驗證可以透過ValidationRules 實現, 然而在實際情況中, 驗證除了Pass / Failure 外, 還可能會有severity level, warning 等. 可惜WPF 本身不支援. 若要加入的話, 最直接的方法只得從ViewModel 著手.
在示範中, 利用DataTrigger 來決定ViewModel 是否valid 及warning. 因為沒有利用WPF 內建的INotifyError 關係, 故直接不用直內置的Validation object.
MainWindow.xaml
<Window x:Class="PoC.WpfWarning.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:PoC.WpfWarning" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:User /> </Window.DataContext> <StackPanel> <TextBlock Text="User Name" /> <TextBox Text="{Binding Name, Mode=TwoWay}" Validation.ErrorTemplate="{x:Null}"> <TextBox.Style> <Style TargetType="TextBox"> <Style.Triggers> <DataTrigger Binding="{Binding HasWarnings}" Value="True"> <Setter Property="Background" Value="Yellow" /> </DataTrigger> <DataTrigger Binding="{Binding Errors}" Value="True"> <Setter Property="Background" Value="Pink" /> </DataTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox> <TextBox /> </StackPanel> </Window>
User.cs
public class User : ObservableObject { private string _name; public string Name { get { return _name; } set { if (_name != value) { SetField(ref _name, value); ValidateProperty(new NameValidor()); } } } }
NameValidator.cs
public class NameValidor : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { ValidationResult result = new WarnedValidationResult(true, false, null); User source = value as User; if (source != null) { if (string.IsNullOrEmpty(source.Name)) result = new ValidationResult(false, "Name could not be empty."); else if (source.Name == "aaa") result = new WarnedValidationResult(true, true, "Message cannot be " + source.Name); } return result; } }
IDataWarningInfo.cs
public interface IDataWarningInfo { event EventHandler<DataErrorsChangedEventArgs> WarningsChanged; System.Collections.IEnumerable GetWarnings(string propertyName); bool HasWarnings { get; } }
ObservableObject.cs
public class ObservableObject : INotifyPropertyChanged, INotifyDataErrorInfo, IDataWarningInfo { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { field = value; OnPropertyChanged(propertyName); return true; } #region Implementation of Interfaces. protected Dictionary<string, ICollection<string>> _validationErrors = new Dictionary<string, ICollection<string>>(); protected Dictionary<string, ICollection<string>> _validationWarnings = new Dictionary<string, ICollection<string>>(); public bool HasErrors { get { return _errors; } set { SetField(ref _errors, value); } } private bool _errors; public bool Errors { get { return _errors; } set { SetField(ref _errors, value); } } private bool _hasWarnings; public bool HasWarnings { get { return _hasWarnings; } set { SetField(ref _hasWarnings, value); } } public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public event EventHandler<DataErrorsChangedEventArgs> WarningsChanged; public virtual void OnErrorsChanged(string propertyName) { if (ErrorsChanged != null) ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } public virtual void OnWarningsChanged(string propertyName) { if (WarningsChanged != null) WarningsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } protected void UpdateValidationErrors(string propertyName, ICollection<string> errorMessage) { if (errorMessage.Count > 0) { /* Update the collection in the dictionary returned by the GetErrors method */ _validationErrors[propertyName] = errorMessage; Errors = true; } else if (_validationErrors.ContainsKey(propertyName)) { /* Remove all errors for this property */ _validationErrors.Remove(propertyName); Errors = false; } /* Raise event to tell WPF to execute the GetErrors method */ OnErrorsChanged(propertyName); OnPropertyChanged(propertyName); } public IEnumerable GetWarnings(string propertyName) { if (string.IsNullOrEmpty(propertyName) || !_validationWarnings.ContainsKey(propertyName)) return null; return _validationWarnings[propertyName]; } protected void UpdateValidationWarnings(string propertyName, ICollection<string> warningMessage) { if (warningMessage.Count > 0) { /* Update the collection in the dictionary returned by the GetErrors method */ _validationWarnings[propertyName] = warningMessage; HasWarnings = true; } else if (_validationWarnings.ContainsKey(propertyName)) { /* Remove all errors for this property */ _validationWarnings.Remove(propertyName); HasWarnings = false; } /* Raise event to tell WPF to execute the GetErrors method */ OnWarningsChanged(propertyName); OnPropertyChanged(propertyName); } IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName) { if (string.IsNullOrEmpty(propertyName) || !_validationErrors.ContainsKey(propertyName)) return null; return _validationErrors[propertyName]; } #endregion public bool ValidateProperty(ValidationRule validator, [CallerMemberName] string propertyName = null) { /* Call service asynchronously */ bool result = false; ValidationResult validationResult = validator.Validate(this, null); if (validationResult != null) { ICollection<string> validationErrors = new List<string>(); ICollection<string> warningMessages = new List<string>(); if (validationResult.IsValid == false) { validationErrors.Add(validationResult.ErrorContent.ToString()); result = validationResult.IsValid; } else if (validationResult is WarnedValidationResult) { WarnedValidationResult warnedValidationResult = validationResult as WarnedValidationResult; if (warnedValidationResult.IsWarned) { warningMessages.Add(warnedValidationResult.ErrorContent.ToString()); result = false; } else { result = true; } } UpdateValidationErrors(propertyName, validationErrors); UpdateValidationWarnings(propertyName, warningMessages); } return result; } }
Leave a Reply