none
互いに関連する複数のデータの検証方法 RRS feed

  • 質問

  • Hoshinaです
    こんにちは

    C#2010 ExpressでWPFの勉強中です。
    疑問点がでてきました。回答をご存じの方がありましたら,ご教授ください。

    2つの正数,「行」と「列」を画面から入力しようとしています。
    int型を2つもつ,ViewModelを2つのTextBoxにバインドしています。
    「行」「列」を単独にデータ検証することは,現在できています。

    仕様上,「行」と「列」の乗数が12以下の制限があります。
    この実装方法が分かりません。

    例:
    「行」が5で「列」が2である状態で,「列」を3に変更します
    「列」のTextBoxの枠が赤くなります
    ToolTipに,エラーを表示することもできています
    この状態で,「行」を4に変更すると,条件が満たされます
    しかし,「列」のTextBoxの枠を通常の状態に戻す方法が分かりません
    もちろん,ToolTipのエラー表示をクリアすることも必要です

    このような場合,どのようにエラー検証を組み込むのが常道でしょうか?
    よろしく,ご意見をお聞かせください

    2011年11月25日 1:27

回答

  • > 「行」「列」を単独にデータ検証することは,現在できています。
    ・・・
    > しかし,「列」のTextBoxの枠を通常の状態に戻す方法が分かりません


    すいません、見落としてました(大汗
    ただし、これは応用だと思います。行プロパティのセッター内で列プロパティを設定しなおせばいいと思います。
    私の場合は、終了日が修正されたら、開始日のエラー検証をリセットするため、以下のような感じでやってます。

    Public Property EndDate() As Date
    	Get
    		Return _EndDate
    	End Get
    	Set(ByVal value As Date)
    		If (_EndDate = value) Then Return
    		_EndDate = value
    
    		_errors("EndDate") = Nothing
    		If (_BeginDate > _EndDate) Then
    			_errors("EndDate") = "開始期日よりも前の日付です。修正してください。"
    		Else
    			Me.BeginDate = Me.BeginDate ' ここで開始期日のエラーをリセットする。
      		End If
    
    		RaisePropertyChanged("EndDate")
    	End Set
    End Property
    

    #また外してしまった・・・(汗

     


    ひらぽん http://d.hatena.ne.jp/hilapon/
    • 回答としてマーク Hoshina 2011年11月25日 4:32
    2011年11月25日 3:37
    モデレータ

すべての返信

  • > 「行」「列」を単独にデータ検証することは,現在できています。

    どのように上記を実装されているのかはわかりませんが、
    MultiBinding、ValidationRules、Validation.ErrorTemplate などを検索すれば解決策が見つかるかもしれません

    そのほか、Validation だと Validation.HasError プロパティとか Validation.Error イベントをトリガにしていろいろできます

    エラー検証ということなので、Validation を利用するのが一般的かと

    2011年11月25日 1:58
  • このような場合,どのようにエラー検証を組み込むのが常道でしょうか?
    よろしく,ご意見をお聞かせください


    私の場合、以下のような IDataErrorInfo 実装クラスの雛形をスニペットに登録して開発してます。ちなみに下のコードでは MVVM インフラに Livet を使ってますが、IDataErrorInfo の実装部分は ViewModel に標準的に使われる INotifyPropertyChanged 実装クラスにも流用できます。

    Option Explicit On
    Option Strict On
    
    Imports Livet
    Imports System.ComponentModel
    
    
    Public Class MyViewModel
    	Inherits ViewModel
    	Implements IDataErrorInfo
    
    #Region "IDataErrorInfo の実装"
    
    	Private ReadOnly _errors As New Dictionary(Of String, String)
    
    	Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error
    		Get
    			Dim ret = String.Empty
    			For Each e In _errors
    				If (Not String.IsNullOrEmpty(e.Value)) Then
    					ret += e.Value + Environment.NewLine
    				End If
    			Next
    			Return ret
    		End Get
    	End Property
    
    	Default Public ReadOnly Property Item(propertyName As String) As String Implements IDataErrorInfo.Item
    		Get
    			If (_errors.ContainsKey(propertyName)) Then
    				Return _errors(propertyName)
    			Else
    				Return Nothing
    			End If
    		End Get
    	End Property
    
    #End Region
    
    End Class
    

     

    ViewModel のプロパティにエラー検証を設けた例です。こんな感じで使っています。

    #Region "BeginDate変更通知プロパティ"
    	Private _BeginDate As Date
    
    	Public Property BeginDate() As Date
    		Get
    			Return _BeginDate
    		End Get
    		Set(ByVal value As Date)
    			' If (_BeginDate = value) Then Return
    			_BeginDate = value
    
    			_errors("BeginDate") = Nothing
    			If (_BeginDate > _EndDate) Then
    				_errors("BeginDate") = "終了期日よりも後の日付です。修正してください。"
    			End If
    
    			RaisePropertyChanged("BeginDate")
    		End Set
    	End Property
    #End Region
    

    View は こんな感じでバインドしてます。

    <im:GcDateTime Name="GcDateTime1" Value="{Binding BeginDate, Mode=TwoWay, ValidatesOnDataErrors=True}" />
    

    > もちろん,ToolTipのエラー表示をクリアすることも必要です

    私は GrapeCity 社の InputMan for WPF に含まれる GcValidationIndicator を使ってます。要素を指定するだけでエラー発生時にアイコンが点滅しツールチップが表示されるので非常に楽です。

    <im:GcValidationIndicator ElementName="GcBeginDate"/>
    

     


    ひらぽん http://d.hatena.ne.jp/hilapon/
    2011年11月25日 2:34
    モデレータ
  • ちなみに私 WPF はまだまだ修行中な上、上記もあくまで私の事例ですので 「常道」 とは言いません。あしからず。


    GcValidationIndicator を使う前は、対象となるコントロールの横に TextBlock を配置し、エラー検証時は赤文字でメッセージを表示するようにしていました。

    <TextBlock Foreground="Red"
    	Text="{Binding ElementName=GcDateTime1, 
    	Path=(Validation.Errors).CurrentItem.ErrorContent}"/>

    ただし WPF + MVVM で開発してるとどうしても工数がかさみますので、サードパーティー製のツールを使って工数短縮図るのは有効な手段だと思ってます。10万~20万かかっても、使わない時の工数考えると遥かに安上がりですしね。

     

     


    ひらぽん http://d.hatena.ne.jp/hilapon/
    2011年11月25日 2:55
    モデレータ
  • > 「行」「列」を単独にデータ検証することは,現在できています。
    ・・・
    > しかし,「列」のTextBoxの枠を通常の状態に戻す方法が分かりません


    すいません、見落としてました(大汗
    ただし、これは応用だと思います。行プロパティのセッター内で列プロパティを設定しなおせばいいと思います。
    私の場合は、終了日が修正されたら、開始日のエラー検証をリセットするため、以下のような感じでやってます。

    Public Property EndDate() As Date
    	Get
    		Return _EndDate
    	End Get
    	Set(ByVal value As Date)
    		If (_EndDate = value) Then Return
    		_EndDate = value
    
    		_errors("EndDate") = Nothing
    		If (_BeginDate > _EndDate) Then
    			_errors("EndDate") = "開始期日よりも前の日付です。修正してください。"
    		Else
    			Me.BeginDate = Me.BeginDate ' ここで開始期日のエラーをリセットする。
      		End If
    
    		RaisePropertyChanged("EndDate")
    	End Set
    End Property
    

    #また外してしまった・・・(汗

     


    ひらぽん http://d.hatena.ne.jp/hilapon/
    • 回答としてマーク Hoshina 2011年11月25日 4:32
    2011年11月25日 3:37
    モデレータ
  • 仕様上,「行」と「列」の乗数が12以下の制限があります。
    この実装方法が分かりません。

    例:
    「行」が5で「列」が2である状態で,「列」を3に変更します
    「列」のTextBoxの枠が赤くなります
    ToolTipに,エラーを表示することもできています
    この状態で,「行」を4に変更すると,条件が満たされます
    しかし,「列」のTextBoxの枠を通常の状態に戻す方法が分かりません
    もちろん,ToolTipのエラー表示をクリアすることも必要です

    このような場合,どのようにエラー検証を組み込むのが常道でしょうか?
    よろしく,ご意見をお聞かせください


    ちょっと考えてみました。以下のサンプルで希望の動作をします。サードパーティツールを使ってますが、各プロパティの実装だけ見て頂ければいいと思います。 

    Option Explicit On
    Option Strict On
    
    Imports Livet
    Imports System.ComponentModel
    
    
    Public Class MainWindowViewModel
        Inherits ViewModel
        Implements IDataErrorInfo
    
    #Region "Column変更通知プロパティ"
        Private _Column As Integer
    
        Public Property Column() As Integer
            Get
                Return _Column
            End Get
            Set(ByVal value As Integer)
                _errors("Column") = Nothing
                If (_Column <> value) Then
                    _Column = value
                    If Not Me.Validate Then
                        _errors("Column") = "オーバーしてます。"
                    Else
                        Me.Row = Me.Row
                    End If
                End If
                RaisePropertyChanged("Column")
            End Set
        End Property
    #End Region
    
    #Region "Row変更通知プロパティ"
        Private _Row As Integer
    
        Public Property Row() As Integer
            Get
                Return _Row
            End Get
            Set(ByVal value As Integer)
                _errors("Row") = Nothing
                If (_Row <> value) Then
                    _Row = value
                    If Not Me.Validate Then
                        _errors("Row") = "オーバーしてます。"
                    Else
                        Me.Column = Me.Column
                    End If
                End If
                RaisePropertyChanged("Row")
            End Set
        End Property
    #End Region
    
    #Region "メソッド"
    
        Private Function Validate() As Boolean
            Return (Me.Column * Me.Row) <= 12
        End Function
    
    #End Region
    
    #Region "IDataErrorInfo の実装"
    
        Private ReadOnly _errors As New Dictionary(Of String, String)
    
        Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error
            Get
                Dim ret = String.Empty
                For Each e In _errors
                    If (Not String.IsNullOrEmpty(e.Value)) Then
                        ret += e.Value + Environment.NewLine
                    End If
                Next
                Return ret
            End Get
        End Property
    
        Default Public ReadOnly Property Item(propertyName As String) As String Implements IDataErrorInfo.Item
            Get
                If (_errors.ContainsKey(propertyName)) Then
                    Return _errors(propertyName)
                Else
                    Return Nothing
                End If
            End Get
        End Property
    
    #End Region
    
    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:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
            xmlns:local="clr-namespace:LivetWPFApplication1"
            xmlns:im="http://schemas.grapecity.com/windows/2010/inputman"
            Title="MainWindow" Height="130" Width="300" >
        
        <Window.DataContext>
            <local:MainWindowViewModel />
        </Window.DataContext>
        
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="Auto" />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="80"/>
                <ColumnDefinition Width="30" />
                <ColumnDefinition Width="80"/>
                <ColumnDefinition Width="30" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <im:GcNumber Grid.Row="1" Grid.Column="1" Name="GcNumber1" Value="{Binding Column, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}" />
            <im:GcNumber Grid.Row="1" Grid.Column="3" Name="GcNumber2" Value="{Binding Row,UpdateSourceTrigger=PropertyChanged,  Mode=TwoWay, ValidatesOnDataErrors=True}" />
            <im:GcValidationIndicator Grid.Row="1" Grid.Column="2" Height="16" Width="16" ElementName="GcNumber1" />
            <im:GcValidationIndicator Grid.Row="1" Grid.Column="4" Height="16" Width="16" ElementName="GcNumber2" />
        </Grid>
    </Window>
    


    ひらぽん http://d.hatena.ne.jp/hilapon/
    2011年11月25日 4:24
    モデレータ
  • Hoshinaです
    こんにちは

    >ひらぽんさん
    > Me.BeginDate = Me.BeginDate

    実は,これやってみてダメだったはず,と思いましたが,気がつきました。
    私はC#ですが,次のようにプロパティ経由ではなく,変数に直接代入していました。
    これでは,ダメですね。
    プロパティ経由で行えば,再度エラー検証が行われますので,正しい結果となりました。

    × this._row = this._row;     // _rowは問題の変数名

    ○ Row = Row;           // Rowはプロパティ

    ありがとうございました

    2011年11月25日 4:32
  • 個人的には、

    • 「列」のエラー
    • 「行」のエラー
    • 「行と列の組み合わせ」のエラー

    の3つのエラーを提供しておいて、UI 側で2つのエラーを確認するようにするとシンプルなのでは?と思います。「行」へ入力したことで「列」のエラーが解消されるのは、設計としてずれがあると感じます。

    「列」に表示するエラーは「列」と「行と列の組み合わせ」の2つのエラーです。「行」へ入力して解消するのは「行」のエラーと「行と列の組み合わせ」のエラーであり、「行と列の組み合わせ」に対するエラーが解消することで「列」のエラーの表示がなくなるようになり、自然な形になるのではないでしょうか?

    こうすることで、単独項目のエラーと組み合わせによるエラーに対するメッセージを違うものにしたり、UI においてエラーの表示方法を変更するようなこともできるようになります。(もちろん、2種類のエラーを同じ表現にすることも)

    2011年11月29日 0:36
  • Hoshinaです
    こんにちは

    K.Takaokaさんの投稿,概念的にはわかる気がするのですが,実装方法が思い浮かびません。

    画面の構成要素には,以下の2つしかない想定です。
    ・列数を入力するためのTextBox
    ・行数を入力するためのTextBox

    そのため,「行と列の組み合わせのエラー」を担当する部分が無いので,TextBoxの枠が赤くなるのは,上記のどちらかのTextBoxになります。もしかすると,「行×列」を表示する読出し専用のTextBoxを用意するという想定でしょうか?

    TextBoxを2つしか用意しない場合,「列数」の入力によって検出されたエラーであっても,「行数」の再入力でエラー状態を回避できますので,ひらぽんさんから提示してもらった解法がこの場合には適切と判断していますが,いかがでしょうか?

    2011年12月1日 0:10