Silverlight 4 Betaで追加されたINotifyDataErrorInfoを使った検証
Silverlight 4 Betaでは、これまでWPFやWindowsフォームでサポートされていたIDataErrorInfoが追加されただけでなく、WPFにはないINotifyDataErrorInfoという新しいインターフェイスも追加されています。これによりIDataErrorInfoにおける以下の問題が解決されます。
- 1つの検証では1つのエラーしか返せない
- エラー内容はStringのみとなっている
- 検証のタイミングはBindingに委ねられており、任意のタイミングで検証できない
ここでは3つ目の「任意のタイミングで検証を実行する」という部分を中心に最小限のサンプルを用いて解説してきたいと思います。たとえばXとYの2つの数値があり、XはYよりも小さくなければならないという例題で考えます。
まずはIDataErrorInfoを使って実装した場合です。
ViewModel.vb(Visual 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
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="<" VerticalAlignment="Center" />
<TextBox Width="80"
Text="{Binding Y, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
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="<" VerticalAlignment="Center" />
<TextBox Width="80"
Text="{Binding Y, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
MainPage.xaml.vb(Visual Basic)
Partial Public Class MainPage
Inherits UserControl
Public Sub New()
InitializeComponent()
Me.DataContext = New ViewModel With {.X = 10, .Y = 20}
End Sub
End Class
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.vb(Visual 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
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="<" VerticalAlignment="Center" />
<TextBox Width="80"
Text="{Binding Y, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
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="<" VerticalAlignment="Center" />
<TextBox Width="80"
Text="{Binding Y, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
MainPage.xaml.vb(Visual Basic)
IDataErrorInfoのコードと同じ
INotifyDataErrorInfoの場合、検証はErrorsChangedイベントが発生したタイミングで行われるため、Yの値を変更した際にもXの検証が行われるように実装することが可能です。
このようなシナリオのほかに、検証がクライアントで完結せずにサーバーに問い合わせる必要がある場合、結果は非同期でしか受け取れないため任意のタイミングで検証を行うということが必要になります。
Fredrik Normén - Silverlight 4 and Asynchronous Validation with INotifyDataErrorInfo
上記のページに掲載されているC#のサンプルコードをほぼそのままVisual Basicで書き直したプロジェクトを下記の場所においておきますので、参考にしていただければと思います。
サーバー側でThread.Sleepを使った3秒待つようにしているので、"12345"と入力してフォーカスを抜けてから3秒+α後に検証エラーが表示されるはずです。