WPF Prism episode: 10 ~ ErrorTemplate は Resources タグ、時々、ResourceDictionary ファイルのなか。~
前回は ReactiveProperty に Validation を設定する方法を 2 回に分けても全て紹介出来なかったので、今回も前回からの続きで、Validation のエラーメッセージを View へ表示するための ErrorTemplate を定義する方法を紹介します。
今回から 1 エントリに書く文字数を減らして、Validation の ErrorTemplate の紹介のみになっているので早めの更新になりました。(理由はエントリの最後へ)
尚、この記事は Visual Studio 2017 Community Edition で .NET Framework 4.7.2 以上 と C# + Prism 7.1 + ReactiveProperty + Extended WPF Toolkit™ を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。
Validation の ErrorTemplate
前回、Validation に設定したエラーメッセージが View に表示されなかったのは、WPF 標準のエラー表示テンプレートがエラーメッセージの表示領域が用意されていないのが理由なので、Validation のエラーメッセージを View へ表示するためには別途エラー表示用のテンプレートを定義して、その中にエラーメッセージを表示するエリアを追加する必要があります。
尚、以降で紹介する ErrorTemplate は SourceChord の『WPFでの入力値検証・その6 ~複数のエラー表示への対応~』 で紹介されている Template をそのまま流用させていただいています。
対象のXAML(ここでは PhysicalEditor.xaml)へ以下のように Resources 属性を追加して、その中に ControlTemplate を定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <UserControl x:Class="WpfTestApp.Views.PhysicalEditor" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> <UserControl.Resources> <ControlTemplate x:Key="ValidationTemplate"> <StackPanel> <ItemsControl ItemsSource="{Binding AdornedElement.(Validation.Errors), ElementName=validationTarget}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Foreground="Red" Text="{Binding ErrorContent}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <AdornedElementPlaceholder x:Name="validationTarget" /> </StackPanel> </ControlTemplate> </UserControl.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="0.4*"/> <RowDefinition Height="0.6*"/> </Grid.RowDefinitions> ~ 略 ~ </Grid> </UserControl> |
追加した ControlTemplate へ x:Key 属性で名前を設定します。(ここでは『ValidationTemplate』)
x:Key 属性に指定した名前で ControlTemplate を参照することができます。
ControlTemplate 内の【AdornedElementPlaceholder】は Validation 中のコントロールを表し、View へ描画する際に対象コントロールへ置き換えられます。
又、Validation のエラーメッセージは Validation.Errors プロパティへバインドした ItemsControl 内の TextBlock コントロールへ表示しています。
上記以外のテンプレートについての詳細は、SourceChord の『WPFでの入力値検証・その6 ~複数のエラー表示への対応~』 を参照してください。
追加したテンプレートを使用するには、コントロール自体にも使用するテンプレート名を指定する必要があり、以下のように対象コントロールの Validation.ErrorTemplate プロパティへ使用するテンプレート名を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <UserControl x:Class="WpfTestApp.Views.PhysicalEditor" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> ~ 略 ~ <Grid> ~ 略 ~ <Grid Grid.Column="1" Grid.Row="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.25*"/> <ColumnDefinition Width="0.25*"/> <ColumnDefinition Width="0.25*"/> <ColumnDefinition Width="0.25*"/> </Grid.ColumnDefinitions> <DatePicker Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Center" Margin="10,0" SelectedDate="{Binding MeasurementDate.Value, UpdateSourceTrigger=PropertyChanged}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}" /> </Grid> ~ 略 ~ <Grid Grid.Column="0" Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.8*"/> <ColumnDefinition Width="0.2*"/> </Grid.ColumnDefinitions> <TextBox Grid.Column="0" Text="{Binding Height.Value, UpdateSourceTrigger=PropertyChanged}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}" MaxLength="6" TextWrapping="WrapWithOverflow" VerticalContentAlignment="Center" HorizontalContentAlignment="Right" /> <TextBlock Grid.Column="1" VerticalAlignment="Center" Text="cm" /> </Grid> <Grid Grid.Column="1" Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.8*"/> <ColumnDefinition Width="0.2*"/> </Grid.ColumnDefinitions> <TextBox Grid.Column="0" Text="{Binding Weight.Value, UpdateSourceTrigger=PropertyChanged}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}" MaxLength="6" TextAlignment="Right" VerticalContentAlignment="Center" /> <TextBlock Grid.Column="1" VerticalAlignment="Center" Text="kg" /> </Grid> <TextBox Grid.Column="3" Grid.Row="1" Text="{Binding Bmi.Value, Mode=OneWay}" TextAlignment="Right" IsReadOnly="True" VerticalContentAlignment="Center" /> </Grid> </Grid> </Grid> </UserControl> |
ここまで記述して実行すると、WPF 標準エラーテンプレートが上書きされるため赤枠が表示されなくなり、以下のように エラーメッセージのみ表示されるようになります。
ただ、このサンプルアプリのレイアウトのままでは、身長プロパティと体重プロパティの両方が Validation でエラーになった場合、エラーメッセージが重なって表示されてしまうため、画面レイアウトを再考する必要があると思いますが、ここでは修正しません。
実際はアプリの UI 設計時にエラーメッセージの表示先まで考慮して設計することが必要です。
(サンプルアプリのレイアウトを修正する余裕がないのでこのまま続けます)
ResourceDictionary ファイル
これで身体測定データ編集 View ではエラーメッセージが表示されるようになったので、他 View へも前章と同じ方法で展開すればエラーメッセージが表示されるようになりますが、ErrorTemplate をそれぞれの View へ追加してしまうと、変更が必要になった場合に全 View の変更が必要になるので、どこか 1 か所にまとめて定義したくなるはずです。
通常の WPF アプリの場合であれば、前章で紹介した ErrorTemplate を App.xaml へアプリケーションリソースとして定義すれば共通リソースとして使用できますが、Prism の Module として作成した DLL アセンブリから App.xaml へ定義したアプリケーションリソースへアクセスしようとすると例外が発生します。
このサンプルアプリも同様に App.xaml へ アプリケーションリソースとして定義した ErrorTemplate にアクセスしようとしても実行時例外が発生してアクセスできませんでした。
このサンプルアプリでは ErrorTemplate を使用する全 View が単一のプロジェクトへ配置されているので、EditorViews プロジェクトへ共通リソースディクショナリファイルを追加してその中へ定義します。
共通リソースを追加するには以下のように Visual Studio の【新しい項目の追加】から【リソースディクショナリ (WPF)】を選択してプロジェクトの直下に追加します。
(ここではプロジェクトの直下に追加していますが、追加場所はどこでも構いません)
追加したリソースディクショナリファイルへ身体測定データ編集 View で定義した ErrorTemplate を以下のように貼り付けて、身体測定データ編集 View の XAML 側定義は削除します。
(前章で紹介した ControlTemplate をそのまま貼り付けただけで中身は同じです)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfTestApp"> <ControlTemplate x:Key="ValidationTemplate"> <StackPanel> <AdornedElementPlaceholder x:Name="validationTarget" /> <ItemsControl ItemsSource="{Binding AdornedElement.(Validation.Errors), ElementName=validationTarget}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Foreground="Red" Text="{Binding ErrorContent}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </ControlTemplate> </ResourceDictionary> |
そして、View の XAML へ定義していた Resources 属性内の ControlTemplate の定義を、以下のようにリソースディクショナリを読み込むように変更するだけで、リソースディクショナリファイルへ移動した ErrorTemplate を共通リソースとして使用できるようになります。
※ コントロールへ設定した ErrorTemplate 名等の変更は不要
1 2 3 4 5 6 7 8 | <UserControl x:Class="WpfTestApp.Views.PhysicalEditor" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> <UserControl.Resources> <ResourceDictionary Source="pack://application:,,,/EditorViews;component/EditorViewsResource.xaml" /> </UserControl.Resources> ~ 略 ~ </UserControl> |
但し、リソースディクショナリファイルをプロジェクトの直下に追加したからと言って、読込先のパスに【EditorResources.xaml】等とファイル名だけを指定すると読み込みに失敗して例外が発生するので、上記 5 行目のように episode: 5 で紹介したパック URI スキームの記法でリソースディクショナリファイルを指定する必要があります。
リソースディクショナリファイルのパスを正しく設定した後に実行して確認すると、身体測定データ編集 View ではリソースディクショナリを追加する前と変わらず動作するはずですし、他の View でもエラーメッセージが表示されるようになっているはずです。
note 各 View の各コントロールへ Validation.ErrorTemplate プロパティが指定されている必要があります。
このようにテンプレートや各コントロールのスタイルなどをリソースディクショナリへ定義すると、いろんな画面で使い回すことができて統一的な見栄えのアプリが作成できるので、管理人もこれから活用していきたいと思っていますが、まだまだ WPF に慣れていないので活用しきれていません…
さて、3 回に分けて紹介してきた ReactiveProperty で定義したプロパティへ Validation を設定する方法の紹介は、ようやくここで終了になります。
実は Validation についてはもう少し書きたいネタがいくつかあるにはあるのですが、現時点では検証の準備すら始められていませんし、Prism + ReactiveProperty 入門というテーマからは外れてしまうので、この連載が終了した以降にでも書ければと思っています。
今回から 1 エントリの文字数を試験的に少なくすることにしました。
ここ数回のエントリは 1 エントリ当り 20,000 ~ 25,000 文字(ソースコード含む)くらいのボリュームでしたが、今回からは少なくとも今までの半分 ~ 1/3 程度のボリュームに抑えようと思っています。
この連載を書き始めた当初は 1 エントリ 2,000 文字、5,000 文字程度でしたが、episode を重ねるごとに文字数が増えて episode:5 以降は 20,000 文字オーバーを目安に書くようになってしまい、推敲のために読み返すのにも結構大変で、タイトルと内容が乖離しているエントリも出て来て気になっていました。
文字数に拘っている訳ではないので、書く内容は今までと変える予定はありませんが、タイトルからあまりかけ離れない内容に分割して書いていこうと思っています。
とりあえず試験的に変えてみたので又、元に戻るかもしれません。
居るとは思えませんが、もし episode:5 ~ episode:9 位のボリュームの方が良い!と言う意見等ありましたらコメントでも頂ければ検討します。
Validation の紹介は以上になるので、次回は前回予告していた Prism の IConfirmNavigationRequest インタフェースを紹介する予定で、次回も今までと比べると少し早めの更新になると思います。
そして今回紹介したソースコードもいつもの通り GitHub リポジトリへアップしています。