Silverlight 3におけるValidatesOnDataErrorsの代替コントロールを実装してみた(暫定版)
概要
Silverlight 3だとValidatesOnDataErrorsがないので、代替のコントロールを実装してみた。[Silverlight][C#][VBも挑戦]Silverlight2での入力値の検証 その4とほとんど同じ内容。INotifyDataErrorInfoにしてるけど、現状利点なしな使い方。暫定版。
流れ
- INotifyDataErrorInfoとINotifyPropertyChangedを実装したViewModelBaseを作成。
- ViewModelBaseを継承したMyViewModelをMainPageにバインディング(バインド部分は省略)。
- MyViewModelでの検証エラーはViewModelBaseのerrors(INotifyDataErrorInfoのGetErrorsで取得可能)に保持される。
- DataErrorControlではDataContext(INotifyDataErrorInfo)を監視して、エラーを取得し、あれば表示。
ファイル一覧
- Common/INotifyDataErrorInfo
- Common/DataErrorsChangedEventArgs
- Common/DataErrorControl
- Common/ViewModelBase
- MyViewModel
- MainPage
暫定版ソースコード
Common/INotifyDataErrorInfo
using System; using System.Collections; namespace SilverlightApplicationIDataErrorInfo.Common { public interface INotifyDataErrorInfo { IEnumerable GetErrors(string propertyName); bool HasError { get; } event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; } }
Common/DataErrorsChangedEventArgs
using System; namespace SilverlightApplicationIDataErrorInfo.Common { public sealed class DataErrorsChangedEventArgs : EventArgs { public string PropertyName { get; set; } public DataErrorsChangedEventArgs(string propertyName) { this.PropertyName = 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; // oldContext.PropertyChanged -= OnDataContextChanged; var newContext = e.NewValue as INotifyDataErrorInfo; if (newContext != null) newContext.ErrorsChanged += OnErrorsChanged; // newContext.PropertyChanged += OnDataContextChanged; UpdateState(); } private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) { if (e.PropertyName == this.PropertyName) UpdateState(); } //private void OnDataContextChanged(object sender, PropertyChangedEventArgs e) //{ // if (e.PropertyName == this.PropertyName) // UpdateState(); //} private void UpdateState() { var errorInfo = this.DataContext as INotifyDataErrorInfo; if (errorInfo == null) { ErrorMessage = null; return; } foreach (var error in errorInfo.GetErrors(this.PropertyName)) { ErrorMessage = error as string; return; } ErrorMessage = null; // 正しくセットできていればクリア } #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 } }
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); } } } }
MyViewModel
namespace SilverlightApplicationIDataErrorInfo { public class MyViewModel : Common.ViewModelBase { private string _Name; public string Name { get { return this._Name; } set { this._Name = value; Validate("Name", value); OnPropertyChanged("Name"); } } private void Validate(string propertyName, string value) { if (propertyName.Equals("Name")) { if (string.IsNullOrEmpty(value)) AddError(propertyName, "required"); else RemoveError(propertyName, "required"); if (value.Length > 5) AddError(propertyName, "stringlength < 5"); else RemoveError(propertyName, "stringlength < 5"); } } } }
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 Background="White"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Name:" /> <TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnExceptions=True}" 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> </UserControl>