[MVVM] 建立 Warning Control

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;
        }
    }

 

About C.H. Ling 260 Articles
a .net / Java developer from Hong Kong and currently located in United Kingdom. Thanks for Google because it solve many technical problems so I build this blog as return. Besides coding and trying advance technology, hiking and traveling is other favorite to me, so I will write down something what I see and what I feel during it. Happy reading!!!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.