VB のたまご

作成日: 2017/03/22, 更新日: 2017/03/22


検証時エラーから復帰すると例外エラー発生してしまう

  •  例えば、人材管理、または社員情報システムを作るなら、個人情報を登録する画面があって、個人名は入力必須にしたいです。 書庫管理システムなら、本の情報を登録する画面があって、本の名前は入力必須にしたいです。在庫管理システムでも、商品名は入力必須にしたいです。

  •  このように、ユーザーがデータ入力する場所によっては必須項目というものがあり、ここはちゃんと入力してくれたかチェックしたいです。 今回は入力検証機能を使ってチェックするやり方を採用したとします。ここでは以下のような簡易サンプルを作成したとします。

  •  ※以下のサンプルでは、Model と View をバインドしたアプリケーションで、Model はベースクラスとそれを継承したデータクラス、そして、View の3つです。 標準の WPF アプリケーションで作成していて、参照設定に「System.ComponentModel.DataAnnotations.dll」を追加しています。 C# と VB それぞれ記載しています。

  •  GitHub にもソースをアップしていますので、手っ取り早くソースを準備したい場合は以下から取得してください。

  • GitHub
    https://github.com/sutefu7/ValidationErrors
    

  •  C# の場合

  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Runtime.CompilerServices;
    using System.Collections;
    
    namespace ValidationErrors
    {
        class NotificationObject : INotifyPropertyChanged, INotifyDataErrorInfo
        {
            #region INotifyPropertyChanged
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
            {
                var handler = PropertyChanged;
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs(propertyName));
            }
    
            #endregion
    
            #region INotifyDataErrorInfo
    
            public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    
            protected void RaiseErrorsChanged([CallerMemberName] string propertyName = "")
            {
                var handler = ErrorsChanged;
                if (handler != null)
                    handler(this, new DataErrorsChangedEventArgs(propertyName));
            }
    
            public bool HasErrors
            {
                get
                {
                    var context = new ValidationContext(this, null, null);
                    var items = new List<ValidationResult>();
                    return !Validator.TryValidateObject(this, context, items, true);
                }
            }
    
            public IEnumerable GetErrors(string propertyName)
            {
                var value = this.GetType().GetProperty(propertyName).GetValue(this, null);
                var context = new ValidationContext(this, null, null) { MemberName = propertyName };
                var items = new List<ValidationResult>();
    
                if (Validator.TryValidateProperty(value, context, items))
                    return null;
    
                return items.Select(x => x.ErrorMessage);
            }
    
            #endregion
    
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using System.ComponentModel.DataAnnotations;
    
    namespace ValidationErrors
    {
        class Model1 : NotificationObject
        {
            string _Name = "taro";
            [Required(ErrorMessage = "必須入力です")]
            public string Name
            {
                get
                {
                    return _Name;
                }
                set
                {
                    if (_Name == value) return;
                    _Name = value;
    
                    this.RaisePropertyChanged();
                    this.RaiseErrorsChanged();
    
                }
            }
        }
    }
    

    <Window x:Class="ValidationErrors.MainWindow"
            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:ValidationErrors"
            mc:Ignorable="d"
            Title="MainWindow" Height="100" Width="300">
        
        <StackPanel Margin="20">
    
            <StackPanel.DataContext>
                <local:Model1 />
            </StackPanel.DataContext>
    
            <TextBox 
                Name="textbox1"
                Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}">
    
                <TextBox.Style>
                    <Style TargetType="TextBox">
                        <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="True">
                                <Setter Property="ToolTip">
                                    <Setter.Value>
                                        <Binding RelativeSource="{RelativeSource Self}" Path="(Validation.Errors)[0].ErrorContent" />
                                        <!--<Binding RelativeSource="{RelativeSource Self}" Path="(Validation.Errors).CurrentItem.ErrorContent" />-->
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TextBox.Style>
            </TextBox>
    
        </StackPanel>
        
    </Window>
    

  •  VB の場合

  • Imports System.ComponentModel
    Imports System.ComponentModel.DataAnnotations
    Imports System.Runtime.CompilerServices
    
    Public Class NotificationObject
        Implements INotifyPropertyChanged, INotifyDataErrorInfo
    
    #Region "INotifyPropertyChanged"
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Protected Sub RaisePropertyChanged(<CallerMemberName> Optional propertyName As String = "")
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    
    #End Region
    
    #Region "INotifyDataErrorInfo"
    
        Public Event ErrorsChanged As EventHandler(Of DataErrorsChangedEventArgs) Implements INotifyDataErrorInfo.ErrorsChanged
    
        Protected Sub RaiseErrorsChanged(<CallerMemberName> Optional propertyName As String = "")
            RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
        End Sub
    
        Public ReadOnly Property HasErrors As Boolean Implements INotifyDataErrorInfo.HasErrors
            Get
    
                Dim context = New ValidationContext(Me, Nothing, Nothing)
                Dim items = New List(Of ValidationResult)
                Return Not Validator.TryValidateObject(Me, context, items, True)
    
            End Get
        End Property
    
        Public Function GetErrors(propertyName As String) As IEnumerable Implements INotifyDataErrorInfo.GetErrors
    
            Dim value = Me.GetType().GetProperty(propertyName).GetValue(Me, Nothing)
            Dim context = New ValidationContext(Me, Nothing, Nothing) With {.MemberName = propertyName}
            Dim items = New List(Of ValidationResult)
    
            If Validator.TryValidateProperty(value, context, items) Then
                Return Nothing
            End If
    
            Return items.Select(Function(x) x.ErrorMessage)
    
        End Function
    
    #End Region
    
    End Class
    

    Imports System.ComponentModel.DataAnnotations
    
    Public Class Model1
        Inherits NotificationObject
    
        Private Property _Name As String = "jiro"
        <Required(ErrorMessage:="必須入力です")>
        Public Property Name As String
            Get
                Return _Name
            End Get
            Set(value As String)
    
                If _Name = value Then Return
                _Name = value
    
                Me.RaisePropertyChanged()
                Me.RaiseErrorsChanged()
    
            End Set
        End Property
    
    End Class
    

    <Window x:Class="MainWindow"
            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:ValidationErrors.VB"
            mc:Ignorable="d"
            Title="MainWindow" Height="100" Width="300">
    
        <StackPanel Margin="20">
    
            <StackPanel.DataContext>
                <local:Model1 />
            </StackPanel.DataContext>
    
            <TextBox 
                Name="textbox1"
                Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}">
    
                <TextBox.Style>
                    <Style TargetType="TextBox">
                        <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="True">
                                <Setter Property="ToolTip">
                                    <Setter.Value>
                                        <Binding RelativeSource="{RelativeSource Self}" Path="(Validation.Errors)[0].ErrorContent" />
                                        <!--<Binding RelativeSource="{RelativeSource Self}" Path="(Validation.Errors).CurrentItem.ErrorContent" />-->
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TextBox.Style>
            </TextBox>
    
        </StackPanel>
    
    </Window>
    

  •  これを実行して、入力欄に記載されている文字列を全て消すと、入力欄が赤枠になり、ツールチップ表示されるようになります。「必須です」と表示されます。 問題はこの後で、何か1文字でも入力すると、以下の例外エラーが発生します。

  • System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0)[0].ErrorContent; DataItem='TextBox' (Name='textbox1'); target element is 'TextBox' (Name='textbox1'); target property is 'ToolTip' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: 指定された引数は、有効な値の範囲内にありません。
    パラメーター名:index'
    

  •  これは、Visual Studio でデバッグ実行した際に表示される例外エラーです。別にシステムエラーが発生して強制終了させられるわけでもなく、ただ出力ウィンドウに表示されるだけです。 デバッグ実行ではなく、exe ファイルを実行した場合も無反応で、やはりシステムエラーが発生して強制終了させられるわけでもありません。

  •  あ、原因は多分 Validation.Errors コレクションプロパティの1つ目のアイテム(のメンバーの ErrorContent )を指定して表示するように xaml 上で書いていますが、エラーじゃなくなったタイミングで、 エラーのコレクションデータが0件になるため、「ArgumentOutOfRangeException: 指定された引数は、有効な値の範囲内にありません。」と言われた系です。

  • ※環境
    Windows 8.1 Pro
    Visual Studio Community 2015 Update 3
    .NET Framework 4.5.2
    

  •  ただ何となく気持ち悪かったため、調べた結果、 Binding to (Validation.Errors)[0] without Creating Debug Spew というスミスさんのブログにたどり着きました(海外サイトなので英語)。 スミスさんの調査によると、エラー内容を指定したい場合は、代わりに CurrentItem プロパティを使うと良いそうで、CurrentItem は null / Nothing にならないとのことです。

  •  つまり、こういう作りなのかなと思いました。

  • 私の勝手なイメージ(間違っているかもしれない)
    (Validation.Errors)?.CurrentItem?.ErrorContent
    

  •  上記より、「(Validation.Errors)[0].ErrorContent」ではなく「(Validation.Errors).CurrentItem.ErrorContent」を指定することで、デバッグ実行しても例外エラーが表示されなくなりました。 コメントアウトしているタグの方ですね。

  •  最後まで読んでいただき、ありがとうございました。