Silverlight 3におけるValidatesOnDataErrorsの代替コントロールを実装してみた(暫定版)その2
概要
ねらいとかは前回の記事を参照。今回の注目は2点。
- 検証方法をDataAnnotationsによるものに切り替えている点
- 検証エラーをプロパティのsetter以外でかけており、それをView(MainPage)に通知できている点
setterでのValidate呼び出しを削除すると分かりやすいです。次あたりErrorMessageプロパティからErrorsプロパティに変更するかもしれない。
MyCommandとかCommandServiceとかは今回の記事には関係がないので割愛。
ソースコード(暫定版)
MainPage
<UserControl x:Class="SilverlightApplicationIDataErrorInfo.MainPage" 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:SilverlightApplicationIDataErrorInfo" xmlns:common="clr-namespace:SilverlightApplicationIDataErrorInfo.Common" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <StackPanel Orientation="Vertical" Background="White"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Name:" Width="100" /> <TextBox Text="{Binding Path=Name, Mode=TwoWay}" Width="250" /> <common:DataErrorControl PropertyName="Name"> <common:DataErrorControl.Template> <ControlTemplate TargetType="common:DataErrorControl"> <TextBlock Text="{TemplateBinding ErrorMessage}" /> </ControlTemplate> </common:DataErrorControl.Template> </common:DataErrorControl> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Message:" Width="100" /> <TextBlock Text="{Binding Message}" Width="250" /> </StackPanel> <Button Content="Update Message" common:CommandService.Command="{Binding UpdateMessageCommand}" common:CommandService.CommandType="ButtonClick" /> </StackPanel> </UserControl>
MyViewModel
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using SilverlightApplicationIDataErrorInfo.Common; namespace SilverlightApplicationIDataErrorInfo { public class MyViewModel : ViewModelBase { #region Constructors public MyViewModel() { this.UpdateMessageCommand = new MyCommand((p) => { Validate("Name"); if (this.HasError) return; this.Message = string.Format("Hello, {0}!", this.Name); }); } #endregion Constructors #region Methods private void Validate(string propertyName) { if (string.IsNullOrEmpty(propertyName) || propertyName.Equals("Name")) { ClearError(propertyName); var results = new List<ValidationResult>(); Validator.TryValidateProperty(this.Name, new ValidationContext(this, null, null) { MemberName = propertyName }, results); foreach (var result in results) { AddError(propertyName, result.ErrorMessage); } } } #endregion Methods #region Properties #region Property Name private string _Name; [Required] [StringLength(5)] public string Name { get { return this._Name; } set { this._Name = value; Validate("Name"); OnPropertyChanged("Name"); } } #endregion Property Name #region Property Message private string _Message; public string Message { get { return this._Message; } set { this._Message = value; OnPropertyChanged("Message"); } } #endregion Property Message #region Property UpdateMessageCommand public MyCommand UpdateMessageCommand { get; private set; } #endregion Property UpdateMessageCommand #endregion Properties } }
Common/ViewModelBase
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; namespace SilverlightApplicationIDataErrorInfo.Common { public class ViewModelBase : INotifyDataErrorInfo, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>(); protected void OnPropertyChanged(string propertyName) { this.VerityPropertyName(propertyName); var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } [Conditional("DEBUG")] [DebuggerStepThrough] private void VerityPropertyName(string propertyName) { Debug.Assert(this.GetType().GetProperty(propertyName) != null, "invalid property name: " + propertyName); } public IEnumerable GetErrors(string propertyName) { if (string.IsNullOrEmpty(propertyName)) { return this.errors.Values; } List<string> errorList; if (this.errors.TryGetValue(propertyName, out errorList)) { return errorList; } else { return new List<string>(); } } public bool HasError { get { return this.errors.Count > 0; } } private void OnDataErrorsChanged(string propertyName) { var handler = ErrorsChanged; if (handler != null) { handler(this, new DataErrorsChangedEventArgs(propertyName)); } } public void AddError(string propertyName, string error) { if (!errors.ContainsKey(propertyName)) errors[propertyName] = new List<string>(); if (!errors[propertyName].Contains(error)) { errors[propertyName].Add(error); OnDataErrorsChanged(propertyName); } } public void RemoveError(string propertyName, string errorMessage) { if (errors.ContainsKey(propertyName) && errors[propertyName].Contains(errorMessage)) { errors[propertyName].Remove(errorMessage); if (errors[propertyName].Count == 0) { errors.Remove(propertyName); } OnDataErrorsChanged(propertyName); } } public void ClearError(string propertyName) { if (errors.ContainsKey(propertyName)) { errors.Remove(propertyName); OnDataErrorsChanged(propertyName); } } } }
Common/DataErrorControl
using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace SilverlightApplicationIDataErrorInfo.Common { public class DataErrorControl : Control { public DataErrorControl() { DefaultStyleKey = typeof(DataErrorControl); SetBinding(MyDataContextProperty, new System.Windows.Data.Binding()); Foreground = new SolidColorBrush(Colors.Red); } #region Property PropertyName public string PropertyName { get { return (string)GetValue(PropertyNameProperty); } set { SetValue(PropertyNameProperty, value); } } #endregion Property PropertyName #region Property ErrorMessage public string ErrorMessage { get { return (string)GetValue(ErrorMessageProperty); } private set { SetValue(ErrorMessageProperty, value); } } #endregion Property ErrorMessage #region Dependency Property PropertyName public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.Register( "PropertyName", // name typeof(string), // propertyType typeof(DataErrorControl), // ownerType new PropertyMetadata(OnPropertyNameChanged)); // defaultMetadata private static void OnPropertyNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var self = obj as DataErrorControl; if (self != null) { self.UpdateState(); } } #endregion Dependency Property PropertyName #region Dependency Property MyDataContext private static readonly DependencyProperty MyDataContextProperty = DependencyProperty.Register( "MyDataContext", // name typeof(object), // propertyType typeof(DataErrorControl), // ownerType new PropertyMetadata(OnMyDataContextChangedStatic)); // defaultMetadata private static void OnMyDataContextChangedStatic(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var self = (DataErrorControl)obj; self.OnMyDataContextChanged(e); } private void OnMyDataContextChanged(DependencyPropertyChangedEventArgs e) { var oldContext = e.OldValue as INotifyDataErrorInfo; if (oldContext != null) oldContext.ErrorsChanged -= OnErrorsChanged; var newContext = e.NewValue as INotifyDataErrorInfo; if (newContext != null) newContext.ErrorsChanged += OnErrorsChanged; UpdateState(); } private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) { if (e.PropertyName == this.PropertyName) UpdateState(); } private void UpdateState() { ErrorMessage = null; var errorInfo = this.DataContext as INotifyDataErrorInfo; if (errorInfo == null) { return; } // 先頭要素をエラーメッセージとして設定 foreach (var error in errorInfo.GetErrors(this.PropertyName)) { ErrorMessage = error as string; return; } } #endregion Dependency Property MyDataContext #region Dependency Property ErrorMessage public static readonly DependencyProperty ErrorMessageProperty = DependencyProperty.Register( "ErrorMessage", // name typeof(string), // propertyType typeof(DataErrorControl), // ownerType new PropertyMetadata(null)); // defaultMetadata #endregion Dependency Property ErrorMessage } }