Silverlight 4 Betaで追加されたINotifyDataErrorInfoを使った検証

http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2009/11/18/silverlight-4-rough-notes-binding-with-inotifydataerrorinfo.aspx



 Silverlight 4 Betaでは、これまでWPFWindowsフォームでサポートされていたIDataErrorInfoが追加されただけでなく、WPFにはないINotifyDataErrorInfoという新しいインターフェイスも追加されています。これによりIDataErrorInfoにおける以下の問題が解決されます。

  • 1つの検証では1つのエラーしか返せない
  • エラー内容はStringのみとなっている
  • 検証のタイミングはBindingに委ねられており、任意のタイミングで検証できない



 ここでは3つ目の「任意のタイミングで検証を実行する」という部分を中心に最小限のサンプルを用いて解説してきたいと思います。たとえばXとYの2つの数値があり、XはYよりも小さくなければならないという例題で考えます。


 まずはIDataErrorInfoを使って実装した場合です。


ViewModel.vbVisual Basic

Imports System.ComponentModel
 
Public Class ViewModel
    Implements IDataErrorInfo
 
    Public Property X
    Public Property Y
 
    Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
        Get
            Throw New NotImplementedException()
        End Get
    End Property
 
    Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
        Get
            Dim result As String = Nothing
 
            If columnName = "X" Then
                If (X >= Y) Then
                    result = "XにはYよりも大きい値を入力してください。"
                End If
            End If
 
            Return result
        End Get
    End Property
End Class


MainPage.xaml
<UserControl x:Class="IDataErrorInfoSample.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"
   d:DesignHeight="300" d:DesignWidth="400" FontSize="16">
 
    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="20">
            <TextBox Width="80"
                    Text="{Binding X, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
            <TextBlock Text="&lt;" VerticalAlignment="Center" />
            <TextBox Width="80"
                      Text="{Binding Y, Mode=TwoWay}"/>
        </StackPanel>
    </Grid>
</UserControl>


MainPage.xaml.vbVisual Basic
Partial Public Class MainPage
    Inherits UserControl
 
    Public Sub New()
        InitializeComponent()
 
        Me.DataContext = New ViewModel With {.X = 10, .Y = 20}
    End Sub
 
End Class





 Xの値を10から30に変更した場合には、Xの値変更によって検証が行われるため検証エラーとなります。一方、Yの値を変えた場合にはXの値の検証は当然ながら実行されません。


 このようなケースではINotifyDataErrorInfoを使うことで、どちらの値を変更してもXの値の検証を実行することが可能になります。


ViewModel.vbVisual Basic
Imports System.ComponentModel
 
Public Class ViewModel
    Implements INotifyDataErrorInfo
 
    Private _x As String
    Public Property X() As String
        Get
            Return _x
        End Get
        Set(ByVal value As String)
            _x = value
            OnErrorsChanged("X")
        End Set
    End Property
 
    Private _y As String
    Public Property Y() As String
        Get
            Return _y
        End Get
        Set(ByVal value As String)
            _y = value
            OnErrorsChanged("X")
        End Set
    End Property
 
    Public Event ErrorsChanged(ByVal sender As Object, ByVal e As System.ComponentModel.DataErrorsChangedEventArgs) Implements System.ComponentModel.INotifyDataErrorInfo.ErrorsChanged
 
    Public Function GetErrors(ByVal propertyName As String) As System.Collections.IEnumerable Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
        Dim result As List(Of String) = Nothing
 
        If propertyName = "X" Then
            If (X >= Y) Then
                result = New List(Of String) From {"XにはYよりも大きい値を入力してください。"}
            End If
        End If
 
        Return result
    End Function
 
    Public ReadOnly Property HasErrors As Boolean Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
        Get
            Throw New NotImplementedException()
        End Get
    End Property
 
    Private Sub OnErrorsChanged(ByVal propertyName As String)
        RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
    End Sub
End Class


MainPage.xaml
<UserControl x:Class="INotifyDataErrorInfoSample.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"
   d:DesignHeight="300" d:DesignWidth="400" FontSize="16">
 
    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="20">
            <TextBox Width="80"
                    Text="{Binding X, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
            <TextBlock Text="&lt;" VerticalAlignment="Center" />
            <TextBox Width="80"
                      Text="{Binding Y, Mode=TwoWay}"/>
        </StackPanel>
    </Grid>
</UserControl>


MainPage.xaml.vbVisual Basic
IDataErrorInfoのコードと同じ





 INotifyDataErrorInfoの場合、検証はErrorsChangedイベントが発生したタイミングで行われるため、Yの値を変更した際にもXの検証が行われるように実装することが可能です。


 このようなシナリオのほかに、検証がクライアントで完結せずにサーバーに問い合わせる必要がある場合、結果は非同期でしか受け取れないため任意のタイミングで検証を行うということが必要になります。

Fredrik Normén - Silverlight 4 and Asynchronous Validation with INotifyDataErrorInfo



 上記のページに掲載されているC#のサンプルコードをほぼそのままVisual Basicで書き直したプロジェクトを下記の場所においておきますので、参考にしていただければと思います。

AsyncINotifyDataErrorInfo_VB.zip



 サーバー側でThread.Sleepを使った3秒待つようにしているので、"12345"と入力してフォーカスを抜けてから3秒+α後に検証エラーが表示されるはずです。