WPFで、グリッド形式(DataGrid + INotifyDataErrorInfo)のデータバインドを行い、入力値の検証と表示をしてみる

前回は単票形式でのWPFのデータバインドとエラー表示を行いました。

一方、自分のまわりではグリッド形式のウィンドウなども見かけるため、今度はグリッド形式でのWPFのデータバインドとエラー表示を行ってみました。

なお、サンプル的なコードなので、厳密なエラーチェックや表示はしていません...

 
2015/12/10 追記 ここから

公式Blogに以下の記事が掲載されましたので、Microsoft.TeamFoundation.MVVMの使用前に記事を確認してみてください。
Microsoft.TeamFoundation.MVVM 名前空間の利用について - Visual Studio サポート チーム blog - Site Home - MSDN Blogs

2015/12/10 追記 ここまで

 

環境

 

作るもの

View-ViewModel-Modelの構成
種類 クラス名 備考
View DataGridView
ViewModel DataGridViewModel データバインド用のプロパティとして、ModelをObservableCollection<Order>として持つ
Model Order INotifyPropertyChangedINotifyDataErrorInfoを実装

 
なお、今回はModelにデータベースを使いません。

また、DataGridViewModelは前回作成したVMBaseを継承して作ります。
VMBaseのソースコードCSharp-Sample/MVVMApp/MVVMApp/VMBase.cs at master - thinkAmi/CSharp-Sample - GitHub

 

画面イメージ
起動時

f:id:thinkAmi:20140717052628p:plain

 

エラー表示(その1)

f:id:thinkAmi:20140717052708p:plain

 

エラー表示(その2)

f:id:thinkAmi:20140717052733p:plain

 

流れ

Modelの作成 (Order.cs)

DataGridにて表示する列をプロパティとして持つ、以下のようなOrderクラスを作成します。

プロパティ名 日本語名 エラーチェック
ID 連番 -
ItemName 商品名 -
Quantity 数量 0以外
UnitPrice 単価 0以外
TotalPrice 合計 -

 
なお、ModelでエラーチェックしてViewに反映させるため、INotifyDataErrorInfoインタフェースを実装しています。

また、Model内部での変更(数量or単価が変更されたら、合計が更新)をViewへと反映させるため、INotifyPropertyChangedインタフェースも実装しています。

 

ViewModelの作成 (DataGridViewModel.cs)

今回はデータベースを使わないので、適当にModelの初期値を設定しておきます。

また、今回のエラーチェックはModelで行っているため、ViewModelではエラーに関する実装はしていません。

 

Viewの作成 (DataGridView.xaml)
データバインドの設定を記載

前回同様、Window要素とDataContext要素を追加します。

<Window ...
    xmlns:local="clr-namespace:MVVMApp"
    ...>
<Window.DataContext>
    <local:DataGridViewModel />
</Window.DataContext>

 

データバインド対象のDataGridを記述

後で変更しますが、最初は以下のような感じで記述します。

<DataGrid AutoGenerateColumns="False" Margin="10" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged, Path=DataGrid}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="ID" IsReadOnly="True" Binding="{Binding Path=ID}" />
        <DataGridTextColumn Header="商品名" Binding="{Binding Path=ItemName}" />
        <DataGridTextColumn Header="数量" Binding="{Binding Path=Quantity, UpdateSourceTrigger=PropertyChanged}" />
        <DataGridTextColumn Header="単価" Binding="{Binding Path=UnitPrice, UpdateSourceTrigger=PropertyChanged}" />
        <DataGridTextColumn Header="合計" Binding="{Binding Path=TotalPrice}" />
    </DataGrid.Columns>
</DataGrid>

 

エラー列を示す、!(exclamation mark - error indicator)への対応

この時点でのViewでは、以下のように、エラーを修正してもエラー列を示す ! は消えません。

f:id:thinkAmi:20140717052809p:plain

f:id:thinkAmi:20140717052832p:plain

f:id:thinkAmi:20140717052842p:plain

 
この挙動についていろいろと調べたところ、.NET4.5のWPFのバグのような情報もありました。

 
そのため、以下を参考に、error indicatorは非表示にする記述を追加しました。
WPF DataGrid validation errors not clearing - Stack Overflow

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow">
        <Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
    </Style>
</DataGrid.RowStyle>

 

エラーのあるセルの表示変更の検討

今のままではエラーのあるセルの表示が赤枠のみで分かりづらいため、セルの表示変更を行います。

なお、DataGridのColumnでDataGridTextColumnを使っている時と、DataGridTemplateColumnを使っている時とでやり方が異なったので、両方の実装を試してみました。

 

エラーのあるセルの表示変更 (DataGridTextColumn編)

DataGridTextColumnは

  • ElementStyle: フォーカスがないときのStyle
  • EditingElementStyle: フォーカスがあるときのStyle

とそれぞれStyleを指定できるので、以下のようにDataGridTextColumn要素を修正します。

<DataGridTextColumn 
    Header="単価" 
    ElementStyle="{StaticResource errorStyleLostFocus}" 
    EditingElementStyle="{StaticResource errorStyleGotFocus}" 
    Binding="{Binding Path=UnitPrice, UpdateSourceTrigger=PropertyChanged}" />

 
次に、上記で指定したerrorStyleLostFocusとerrorStyleGotFocusのStyleを作成します。

Styleで必要な内容は、エラー時は常に

  • 背景色を変更して表示
  • ToolTipでエラー内容を表示

とすることから、両方とも同じようなものになります。

 
次にDataGridTextColumnの挙動を調べてみたところ、

メモ DataGridTextColumn は、TextBlock 要素を非編集モードで作成し、TextBox 要素を編集モードで作成します。
DataGridTextColumn クラス (System.Windows.Controls) - MSDN

とのでした。

TextBlockとTextBoxの共通の祖先にあたるFrameworkElementを見ましたが、背景色を変更するBackgroundプロパティがなく、完全な共通化は難しそうでした。

 
そのため、ToolTipのみ共通化することにしました。
参考: .net - How to apply multiple styles in WPF - Stack Overflow

まずは、エラーがあるときにTootlTipを表示するStyleを記述します。

<Style TargetType="{x:Type FrameworkElement}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip" 
                  Value="{Binding RelativeSource={RelativeSource Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

 
次に、上記のFrameworkElementのStyleをBasedOnを使って拡張し、TextBox向けとTextBlock向けのStyleを記述します。

<Style x:Key="errorStyleLostFocus" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type FrameworkElement}}" >
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="Background" Value="Red"/>
        </Trigger>
    </Style.Triggers>
</Style>
<Style x:Key="errorStyleGotFocus" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type FrameworkElement}}" >
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="Background" Value="Red"/>
        </Trigger>
    </Style.Triggers>
</Style>

 
以上で、DataGridTextColumnを使った時のエラー表示ができました。

 

エラーのあるセルの表示変更 (DataGridTemplateColumn編)

以下を参考に、xamlにDataGridTemplateColumn部分とStyle部分を記述することになります。
DataGrid Validation using IDataErrorInfo in MVVM - WPF | Haris Hasan Blog

 
まずは、数量の列をDataGridTemplateColumnへと変更します。

<DataGridTemplateColumn Header="数量">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBox Style="{StaticResource ResourceKey=textbox}"  Text="{Binding Path=Quantity, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

 
次にStyleを作成します。中身はDataGridTextColumn編とほぼ一緒です(両者で色の区別をつけるためにこちらは背景色をPinkにしてあります)。

<Style x:Key="textbox" TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip" 
                Value="{Binding Path=(Validation.Errors)[0].ErrorContent,
                                RelativeSource={RelativeSource Self}}" />
            <Setter Property="Background" Value="Pink" />
        </Trigger>
    </Style.Triggers>
</Style>

 
以上で、DataGridTemplateColumnを使った時のエラー表示ができました。

 

DataGridTextColumnとDataGridTemplateColumnを使った時の違い

見た目が異なる他、セルにフォーカスを合わせた際、

  • DataGridTextColumn: 初回クリックでセルが選択され、次回クリックで入力が可能になる
  • DataGridTemplateColumn: 初回クリックで入力が可能になる(普通のTextBoxっぽい)

という違いがありました。

他にもあるかもしれませんので、気づいたら追記します。

 

ソースコード

GitHubに上げてあります。
CSharp-Sample/MVVMApp at master - thinkAmi/CSharp-Sample - GitHub

 

参考