かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Silverlight 4のデータ検証

Silverlight 3では、プロパティのsetの部分で値の検証をして、気に入らなければ例外を投げると言うのがセオリーでした。Silverlight 4では、WPFなんかで使われているIDataErrorInfoインターフェースが追加されているので、例外を使わなくても値を保持したままエラー情報を持つことが出来るようになっています。
これに加えて、Silverlight 4では、INotifyDataErrorInfoというIDataErrorInfoを改善したようなインターフェースが定義されています。

// INotifyDataErrorInfo
interface INotifyDataErrorInfo
{
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        public System.Collections.IEnumerable GetErrors(string propertyName);
        public bool HasErrors { get; }
}

こいつは、ErrorsChangedイベントで、特定のプロパティのエラーに変更があったことを通知して、GetErrorsで、プロパティ名指定でエラーを返すようになっています。HasErrorsで、何かエラーがあるかを問い合わせることもサポートしています。
GetErrorsメソッドの戻り値を見てもわかるように、IDataErrorInfoと違い、1プロパティに対して複数のエラーを返すことが出来る上に、string型以外もエラーとして返すことが出来るようになっています。

使ってみよう

説明にも飽きてきたので、使ってみます。今回は、いつものPersonクラスを題材にやっていこうと思います。まずは、INotifyDataErrorInfoを使わない状態でアプリをくんでいきます。SLDataErrorというプロジェクトを新規作成して作っていきます。
最初に、Personクラスです。

// Person.cs
namespace SLDataError
{
    public class Person
    {
        private string _fullName;
        public string FullName
        {
            get { return _fullName; }
            set
            {
                _fullName = value;
            }
        }
    }
}

次に、MainPageにコントロールを置いていきます。とりえあずデザイナーでポトペタやって以下のような画面にします。

そして、Personクラスをバインドしていきます。全部XAMLに書いちゃいました。

<UserControl x:Class="SLDataError.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"
             mc:Ignorable="d"
             xmlns:local="clr-namespace:SLDataError"
             d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.DataContext>
        <local:Person FullName="田中 太郎" />
    </UserControl.DataContext>
    <Grid x:Name="LayoutRoot" Background="White">
        <TextBlock HorizontalAlignment="Left" Margin="12,12,0,0" Name="textBlock1" Text="FullName" VerticalAlignment="Top" />
        <TextBox Text="{Binding Path=FullName, Mode=TwoWay}" Height="24" HorizontalAlignment="Left" Margin="72,8,0,0" Name="textBox1" VerticalAlignment="Top" Width="148" />
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,38,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
    </Grid>
</UserControl>

これを実行すると、まだデータの検証もなにもないアプリが起動します。

検証機能の追加

ということで、PersonクラスにINotifyDataErrorInfoインターフェースを実装します。今回は、FullNameには何かしら入力しないといけないという制約をもうける感じで作りました。

using System.ComponentModel;
using System.Diagnostics;
namespace SLDataError
{
    public class Person : INotifyDataErrorInfo
    {
        // FullNameプロパティにエラーがあるかないか
        private bool _hasFullNameError;

        private string _fullName;
        public string FullName
        {
            get { return _fullName; }
            set
            {
                _fullName = value;
                // 空はだめよ
                _hasFullNameError = string.IsNullOrWhiteSpace(_fullName);
                // FullNameプロパティのエラー事情に変化があったことを通知
                OnErrorsChanged("FullName");
            }
        }

        #region INotifyDataErrorInfo Members

        public event System.EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        // ErrorsChangedイベントを発行する
        private void OnErrorsChanged(string propertyName)
        {
            var h = ErrorsChanged;
            if (h != null)
            {
                h(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            // FullNameプロパティでエラーがあったら
            if (propertyName == "FullName" && _hasFullNameError)
            {
                // エラーの内容を返す
                return new[] { "名前には、何か入れてね♪" };
            }

            // それ以外は、何もなし
            return null;
        }

        // なんかエラーがあったらtrue
        public bool HasErrors
        {
            get { return _hasFullNameError; }
        }

        #endregion
    }
}

これを実行すると以下のようになります。
初期表示

この時点でGetErrors("FullName")が一度呼ばれてます。
値を編集して、フォーカスをTextBoxからはずすたびにGetErrors("FullName")が呼ばれます。

空文字を入力したとき

きちんと、検証エラーが表示されています。いい感じだ。