Quantcast
Channel: MSDN Blogs
Viewing all articles
Browse latest Browse all 29128

“Windows 8.1 におけるストア ビジネスアプリの開発”(MVP Community Camp 2014)DEMO解説 #5:ユーザー入力の検証

$
0
0

皆様、こんにちは!

明日からは、いよいよ Build 2014 @ San Franciscoなので、その前に片づけておかないと…ということでw、MVP Community Camp 2014 デモ解説の最後、ユーザー入力の検証です。まず冒頭は、スライドシェアのリンクです。

<Windows 8.1 におけるストア ビジネスアプリの設計と開発>

ユーザー入力の検証に関しては、このトピックになりますので、ご覧ください。

http://msdn.microsoft.com/ja-jp/library/windows/apps/xx13659.aspx

おそらく業務でストアビジネスアプリ、を開発する際には、もっとも重要な機能となってくるでしょう。主要な要素としては、下記のスライドの通り、ValidatableBindableBaseクラス、System.ComponentModel.DataAnnotations 名前空間、そして ValidateProperties メソッド、になります。

image

デモアプリの概要

当日デモでご紹介したものは、こんなアプリでした。まずアプリを起動すると、このような画面になります。

screenshot_03302014_110549

契約業務ボタンをタッチすると、入力用の画面に遷移します(ナビゲーション)。

screenshot_03302014_110553

ドロップダウンリストから製品を選んで、数量を決めて、

screenshot_03302014_110632

注文に追加します。

screenshot_03302014_110650 

このまま注文実行すると、氏名欄は必須なので検証エラーが出ます。

screenshot_03302014_110658

ここを適当に入力しても、電話番号に文字列を入れると、こちらは正規表現で10ケタのチェックディジットを入れてあるので、検証エラーが出ます。

screenshot_03302014_110708

アプリ全体の方は、こちらにこの後あげますので、参考までに見てみてください。元々のサンプルがWindows 8のテンプレートで作成されたもので、それを、日本語化しつつ、8.1 用にターゲットし直していますので、Common フォルダ等も残っていますが、構成はわかり易いかと思います。以下ではユーザー入力の検証の部分についてのみ、順番に解説していきます。

ソリューションの作成

データを入力する

このようなフィールドを持つ画面を作成します。XAML は次に示す通りです。

screenshot_03302014_110553

   1:<prism:VisualStateAwarePage x:Name="pageRoot"
   2:                             x:Class="PrismAppValidation.Views.AddSalePage"
   3:                             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:                             xmlns:local="using:PrismAppValidation.Views"
   6:                             xmlns:common="using:PrismAppValidation.Common"
   7:                             xmlns:behv="using:PrismAppValidation.Behaviors"
   8:                             xmlns:conv="using:PrismAppValidation.Converters"
   9:                             xmlns:prism="using:Microsoft.Practices.Prism.StoreApps"
  10:                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  11:                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  12:                             mc:Ignorable="d"
  13:                             prism:ViewModelLocator.AutoWireViewModel="True">
  14:  
  15:<Page.Resources>
  16:<Style x:Key="ErrorStyle"
  17:                TargetType="TextBlock">
  18:<Setter Property="FontSize"
  19:                     Value="20" />
  20:<Setter Property="Foreground"
  21:                     Value="Red" />
  22:</Style>
  23:<conv:FirstErrorConverter x:Key="FirstErrorConverter" />
  24:</Page.Resources>
  25:  
  26:  
  27:<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  28:<Grid.RowDefinitions>
  29:<RowDefinition Height="140" />
  30:<RowDefinition Height="Auto" />
  31:<RowDefinition Height="*" />
  32:<RowDefinition Height="Auto" />
  33:</Grid.RowDefinitions>
  34:  
  35:<!-- Back button and page title -->
  36:<Grid>
  37:<Grid.ColumnDefinitions>
  38:<ColumnDefinition Width="Auto" />
  39:<ColumnDefinition Width="*" />
  40:</Grid.ColumnDefinitions>
  41:<Button x:Name="backButton"
  42:                     Command="{Binding GoBackCommand}"
  43:                     Style="{StaticResource NavigationBackButtonNormalStyle}" />
  44:<TextBlock x:Name="pageTitle"
  45:                        Grid.Column="1"
  46:                        Text="{StaticResource AppName}"
  47:                        Style="{StaticResource HeaderTextBlockStyle}" />
  48:</Grid>
  49:<Grid Grid.Row="1">
  50:<ComboBox ItemsSource="{Binding Products}"
  51:                       SelectedValue="{Binding SelectedProductId, Mode=TwoWay}"
  52:                       DisplayMemberPath="ProductName"
  53:                       SelectedValuePath="ProductId"
  54:                       HorizontalAlignment="Left"
  55:                       Margin="120,60,0,0"
  56:                       VerticalAlignment="Top"
  57:                       Width="434" FontSize="24" d:IsHidden="True" />
  58:<TextBox Text="{Binding Quantity, Mode=TwoWay}"
  59:                      HorizontalAlignment="Left"
  60:                      Margin="580,60,0,0"
  61:                      TextWrapping="Wrap"
  62:                      VerticalAlignment="Top"
  63:                      Width="99" FontSize="24" />
  64:<Button Content="注文に追加"
  65:                     Command="{Binding AddToOrderCommand}"
  66:                     HorizontalAlignment="Left"
  67:                     Margin="750,55,0,0"
  68:                     VerticalAlignment="Top"
  69:                     FontSize="24" />
  70:<TextBlock Style="{StaticResource BaseTextBlockStyle}"
  71:                        HorizontalAlignment="Left"
  72:                        Margin="120,15,0,0"
  73:                        TextWrapping="Wrap"
  74:                        Text="製品名"
  75:                        VerticalAlignment="Top"
  76:                        Height="25"
  77:                        Width="215" />
  78:<TextBlock Style="{StaticResource BaseTextBlockStyle}"
  79:                        HorizontalAlignment="Left"
  80:                        Margin="580,15,0,0"
  81:                        TextWrapping="Wrap"
  82:                        Text="数量"
  83:                        VerticalAlignment="Top"
  84:                        Height="25"
  85:                        Width="105" />
  86:</Grid>
  87:  
  88:<Grid HorizontalAlignment="Left"
  89:               Margin="120,5,0,0"
  90:               Grid.Row="2"
  91:               VerticalAlignment="Top"
  92:               Width="868">
  93:<Grid.RowDefinitions>
  94:<RowDefinition Height="50" />
  95:<RowDefinition Height="*" />
  96:</Grid.RowDefinitions>
  97:<Grid Grid.Row="0"
  98:                   Margin="5">
  99:<Grid.ColumnDefinitions>
 100:<ColumnDefinition Width="300" />
 101:<ColumnDefinition Width="150" />
 102:<ColumnDefinition Width="150" />
 103:<ColumnDefinition Width="300" />
 104:</Grid.ColumnDefinitions>
 105:<TextBlock Grid.Column="0"
 106:                            Text="製品名"
 107:                            Style="{StaticResource BaseTextBlockStyle}" />
 108:<TextBlock Grid.Column="1"
 109:                            Text="価格"
 110:                            Style="{StaticResource BaseTextBlockStyle}" />
 111:<TextBlock Grid.Column="2"
 112:                            Text="数量"
 113:                            Style="{StaticResource BaseTextBlockStyle}" />
 114:<TextBlock Grid.Column="3"
 115:                            Text="カテゴリー"
 116:                            Style="{StaticResource BaseTextBlockStyle}" />
 117:</Grid>
 118:<ListView x:Name="OrderItemsList"
 119:                       Grid.Row="1"
 120:                       ItemsSource="{Binding CurrentOrderItems}">
 121:<ListView.ItemTemplate>
 122:<DataTemplate>
 123:<Grid>
 124:<Grid.ColumnDefinitions>
 125:<ColumnDefinition Width="300" />
 126:<ColumnDefinition Width="150" />
 127:<ColumnDefinition Width="150" />
 128:<ColumnDefinition Width="300" />
 129:</Grid.ColumnDefinitions>
 130:<TextBlock Grid.Column="0"
 131:                                        Text="{Binding Product.ProductName}" />
 132:<TextBlock Grid.Column="1"
 133:                                        Text="{Binding Product.UnitPrice}" />
 134:<TextBlock Grid.Column="2"
 135:                                        Text="{Binding Quantity}" />
 136:<TextBlock Grid.Column="3"
 137:                                        Text="{Binding Product.Category}" />
 138:</Grid>
 139:</DataTemplate>
 140:</ListView.ItemTemplate>
 141:  
 142:</ListView>
 143:</Grid>
 144:<Grid Grid.Row="3">
 145:<Grid.ColumnDefinitions>
 146:<ColumnDefinition Width="Auto" />
 147:<ColumnDefinition Width="Auto" />
 148:<ColumnDefinition Width="*" />
 149:</Grid.ColumnDefinitions>
 150:<Grid.RowDefinitions>
 151:<RowDefinition Height="Auto" />
 152:<RowDefinition Height="Auto" />
 153:<RowDefinition Height="Auto" />
 154:<RowDefinition Height="Auto" />
 155:<RowDefinition Height="Auto" />
 156:<RowDefinition Height="Auto" />
 157:<RowDefinition Height="Auto" />
 158:</Grid.RowDefinitions>
 159:<TextBlock Grid.Row="0"
 160:                        Grid.Column="0"
 161:                        Text="氏名"
 162:                        Style="{StaticResource BaseTextBlockStyle}"
 163:                        Margin="5" />
 164:<TextBox Grid.Row="0"
 165:                      Grid.Column="1"
 166:                      Text="{Binding Customer.Name, Mode=TwoWay}"
 167:                      Margin="5"
 168:                      Width="500"
 169:                      HorizontalAlignment="Left"
 170:                      behv:HighlightOnErrors.PropertyErrors="{Binding Customer.Errors[Name]}" FontSize="24" />
 171:<TextBlock Grid.Row="0"
 172:                        Grid.Column="2"
 173:                        Style="{StaticResource ErrorStyle}"
 174:                        Text="{Binding Customer.Errors[Name], 
 175:                 Converter={StaticResource FirstErrorConverter}}" />
 176:<TextBlock Grid.Row="1"
 177:                        Grid.Column="0"
 178:                        Text="電話番号"
 179:                        Style="{StaticResource BaseTextBlockStyle}"
 180:                        Margin="5" />
 181:<TextBox Grid.Row="1"
 182:                      Grid.Column="1"
 183:                      Text="{Binding Customer.Phone, Mode=TwoWay}"
 184:                      Margin="5"
 185:                      Width="500"
 186:                      HorizontalAlignment="Left"
 187:                      behv:HighlightOnErrors.PropertyErrors="{Binding Customer.Errors[Phone]}" FontSize="24" />
 188:<TextBlock Grid.Row="1"
 189:                        Grid.Column="2"
 190:                        Style="{StaticResource ErrorStyle}"
 191:                        Text="{Binding Customer.Errors[Phone], Converter={StaticResource FirstErrorConverter}}" />
 192:<TextBlock Grid.Row="2"
 193:                        Grid.Column="0"
 194:                        Text="住所"
 195:                        Style="{StaticResource BaseTextBlockStyle}"
 196:                        Margin="5" />
 197:<TextBox Grid.Row="2"
 198:                      Grid.Column="1"
 199:                      Text="{Binding Customer.Address, Mode=TwoWay}"
 200:                      Margin="5"
 201:                      Width="500"
 202:                      HorizontalAlignment="Left" FontSize="24" />
 203:<TextBlock Grid.Row="3"
 204:                        Grid.Column="0"
 205:                        Text="市区町村"
 206:                        Style="{StaticResource BaseTextBlockStyle}"
 207:                        Margin="5" />
 208:<TextBox Grid.Row="3"
 209:                      Grid.Column="1"
 210:                      Text="{Binding Customer.City, Mode=TwoWay}"
 211:                      Margin="5"
 212:                      Width="500"
 213:                      HorizontalAlignment="Left" FontSize="24" />
 214:<TextBlock Grid.Row="4"
 215:                        Grid.Column="0"
 216:                        Text="都道府県"
 217:                        Style="{StaticResource BaseTextBlockStyle}"
 218:                        Margin="5" />
 219:<TextBox Grid.Row="4"
 220:                      Grid.Column="1"
 221:                      Text="{Binding Customer.State, Mode=TwoWay}"
 222:                      Margin="5"
 223:                      Width="500"
 224:                      HorizontalAlignment="Left" behv:HighlightOnErrors.PropertyErrors="{Binding Customer.Errors[State]}" FontSize="24" />
 225:<TextBlock Grid.Row="4"
 226:                        Grid.Column="2"
 227:                        Style="{StaticResource ErrorStyle}"
 228:                        Text="{Binding Customer.Errors[State], Converter={StaticResource FirstErrorConverter}}" />
 229:<TextBlock Grid.Row="5"
 230:                        Grid.Column="0"
 231:                        Text="郵便番号"
 232:                        Style="{StaticResource BaseTextBlockStyle}"
 233:                        Margin="5" />
 234:<TextBox Grid.Row="5"
 235:                      Grid.Column="1"
 236:                      Text="{Binding Customer.Zip, Mode=TwoWay}"
 237:                      Margin="5"
 238:                      Width="500"
 239:                      HorizontalAlignment="Left" FontSize="24" />
 240:<Button Grid.Row="6"
 241:                     Grid.Column="1"
 242:                     Content="注文実行"
 243:                     Command="{Binding SubmitOrderCommand}"
 244:                     FontSize="24" />
 245:</Grid>
 246:  
 247:<VisualStateManager.VisualStateGroups>
 248:  
 249:<!-- Visual states reflect the application's view state -->
 250:<VisualStateGroup x:Name="ApplicationViewStates">
 251:<VisualState x:Name="FullScreenLandscape" />
 252:<VisualState x:Name="Filled" />
 253:  
 254:<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
 255:<VisualState x:Name="FullScreenPortrait">
 256:<Storyboard>
 257:<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
 258:                                                        Storyboard.TargetProperty="Style">
 259:<DiscreteObjectKeyFrame KeyTime="0"
 260:                                                     Value="{StaticResource NavigationBackButtonNormalStyle}" />
 261:</ObjectAnimationUsingKeyFrames>
 262:</Storyboard>
 263:</VisualState>
 264:  
 265:<!-- The back button and title have different styles when snapped -->
 266:<VisualState x:Name="Snapped">
 267:<Storyboard>
 268:<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
 269:                                                        Storyboard.TargetProperty="Style">
 270:<DiscreteObjectKeyFrame KeyTime="0"
 271:                                                     Value="{StaticResource NavigationBackButtonNormalStyle}" />
 272:</ObjectAnimationUsingKeyFrames>
 273:<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle"
 274:                                                        Storyboard.TargetProperty="Style">
 275:<DiscreteObjectKeyFrame KeyTime="0"
 276:                                                     Value="{StaticResource NavigationBackButtonNormalStyle}" />
 277:</ObjectAnimationUsingKeyFrames>
 278:</Storyboard>
 279:</VisualState>
 280:</VisualStateGroup>
 281:</VisualStateManager.VisualStateGroups>
 282:</Grid>
 283:</prism:VisualStateAwarePage>
Customer Model オブジェクトを作成

Modelクラスのオブジェクトを作成し、上記の XAMLの各フィールドにバインドする必要があります。そこで、入力の妥当性検証のルール=バリデーション ルールを作ります。Prismでは、Entity Framework 、WCF RIA Servicesなどで使われてきて、大変有名な検証ルール用オブジェクトである System.ComponentModel.DataAnnotationsを使用します。System.ComponentModel.DataAnnotations名前空間内の属性のセットを宣言し、それらを評価するいくつかのサポートクラスの Modelオブジェクトのプロパティに、検証ルールを付与します。

WinRT では、自動的にこれらの属性に基づくルールは評価されないため、いくつかのインフラストラクチャーとしてのコードを作って、これを行わせる必要があります。また、いったんルールが評価されたら、一度発生したエラーをユーザーに表示するために簡単に使用できる方法で格納する必要があります。そこで、Customerクラスのベースクラスとして PrismValidatableBindableBaseクラスを継承して実装します。Modelクラスである Customer.csの内容は、こうなります。

   1:using System;
   2:using System.Collections.Generic;
   3:using System.ComponentModel.DataAnnotations;
   4:using System.Linq;
   5:using Microsoft.Practices.Prism.StoreApps;
   6:  
   7:namespace PrismAppValidation.Models
   8: {
   9:publicclass Customer : ValidatableBindableBase
  10:     {
  11:privatestring _Name;
  12:privatestring _Phone;
  13:privatestring _Address;
  14:privatestring _City;
  15:privatestring _State;
  16:privatestring _Zip;
  17:
  18:         [Required]
  19:publicstring Name
  20:         {
  21:             get { return _Name; }
  22:             set { SetProperty(ref _Name, value); }
  23:         }
  24:  
  25:         [RegularExpression(@"^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$",
  26:             ErrorMessage="10桁の電話番号を数値で入力してください!")]
  27:publicstring Phone
  28:         {
  29:             get { return _Phone; }
  30:             set { SetProperty(ref _Phone, value); }
  31:         }
  32:  
  33:publicstring Address
  34:         {
  35:             get { return _Address; }
  36:             set { SetProperty(ref _Address, value); }
  37:         }
  38:  
  39:publicstring City
  40:         {
  41:             get { return _City; }
  42:             set { SetProperty(ref _City, value); }
  43:         }
  44:  
  45:         [StringLength(5)]
  46:publicstring State
  47:         {
  48:             get { return _State; }
  49:             set { SetProperty(ref _State, value); }
  50:         }
  51:  
  52:publicstring Zip
  53:         {
  54:             get { return _Zip; }
  55:             set { SetProperty(ref _Zip, value); }
  56:         }
  57:     }
  58: }

必須、正規表現、文字列の長さ、等の アトリビュート(属性)を、名前、電話、および県等のプロパティにつき、検証する方法がここに示されています。加えて、数値範囲属性、その他、あらゆる種類の複雑なカスタム検証を実行できる独自のメソッドがあります。

Prism にある ValidatableBindableBaseクラスは、これを継承して作成されたクラスに、いくつかの機能を提供します。まず、このクラスは BindableBase から継承されています。BindableBase は、その背後にあるプロパティの変更時に、当該オブジェクトが正しくデータ バインディングできるように、INotifyPropertyChangedの実装をカプセル化します。BindableBaseは、いくつのヘルパーメソッドを提供します。これにより Customer クラスに示すようなコンパクトなプロパティセッターを記述することができます。ブロックが SetProperty() メンバー変数を設定し、プロパティに設定されている新しい値への参照を渡すようにコールする必要があります。このメソッドは、値が以前と同じかどうかのチェックをカプセル化し、値が無い場合または値が異なる場合には、メンバー変数を設定し、プロパティ変更イベントを発生させます。

ValidatableBindableBaseはまた、BindableValidatorというクラスのインスタンスをカプセル化します。これは、妥当性検証システムのブレーンともいうべき存在で、その点で、ValidatePropertyという名前のメソッドを持ち、あるオブジェクトのために呼ばれると、当該プロパティに対して反映されます。そして当該 DataAnnotation属性により、それらのプロパティを検証し、そのプロパティのためにエラーを集めてエラーのディクショナリにいれます。このディクショナリは、プロパティごとのエラーのリストを保持しています(プロパティ名がディクショナリ中でキーとなり、エラー値はエラー文字列のリストになっています)。ディクショナリはまた、インデクサーを公開しています。これにより、プロパティ名で BindableValidatorをインデックス化でき、そのプロパティに紐づけられたエラー文字列を取り出すことができます。

ValidatableBindableBaseは、BindableValidator から基になるインデクサーを公開し、特定のオブジェクトののエラーのためユーザーに検証の概要を表示する場合、GetAllErrorsメソッドと共にデータバインディングに使用することができます。また ValidatableBindableBaseは、BindableBaseSetPropertyメソッドよりも優先されます。これは、データ バインディングにおいてプロパティが変更されるときに、ValidatePropertyを呼び出すために必要なトリガーポイントとして使用できるようにするためです。そして、WinRT (というよりは XAML )におけるデータバインディングで周知の通り、この状況は、編集されているフィールドにフォーカスの変更があるとき起こります。

Customer クラスの公開とデータバインディング

Customerプロパティを、AddSalesPageViewModelに追加します。しかし、この ViewModel は、OrderRepositoryによって管理される現在の注文に関連付けられているため、当該 ViewModel は、リポジトリから Customerを取得します。

   1:public Customer Customer
   2: {
   3:     get { return _OrderRepository.Customer; }
   4: }

そして、当該リポジトリは、現在の注文に関連付けられている Customer だけを初期化します。実際のアプリでは、注文が完了するか、他の注文を配置する許可を取り消されたときに、注文に関連付けられている現在のデータをクリアするための方法がかならず必要となりますが、このサンプルではそのケースまではカバーされていません。

IOrderRepository.cs

   1:using System;
   2:using System.Collections.Generic;
   3:using System.Collections.ObjectModel;
   4:using System.Linq;
   5:using PrismAppValidation.Models;
   6:  
   7:namespace PrismAppValidation.Services
   8: {
   9:publicinterface IOrderRepository
  10:     {
  11:void AddToOrder(Product product, int quantity);
  12:         ObservableCollection<OrderItem> CurrentOrderItems { get; }
  13:         Customer Customer { get; set; }
  14:     }
  15: }

OrderRepository.cs

   1:using System;
   2:using System.Collections.Generic;
   3:using System.Collections.ObjectModel;
   4:using System.Linq;
   5:using PrismAppValidation.Models;
   6:  
   7:namespace PrismAppValidation.Services
   8: {
   9:publicinterface IOrderRepository
  10:     {
  11:void AddToOrder(Product product, int quantity);
  12:         ObservableCollection<OrderItem> CurrentOrderItems { get; }
  13:         Customer Customer { get; set; }
  14:     }
  15: }
入力検証のエラーをユーザーに表示

エラーが関連付けられているルールがある入力フィールド各々の横に、バインディングされたプロパティのエラーを表示する TextBlockを追加しました。この TextBlock は、そのプロパティに関連付けられたエラーのコレクションにバインドする必要があります。バインディングされたオブジェクトが Customerであり、またそれらのエラーを取得する ValidatableBindableBaseから公開されているインデクサーを持っていることから、XAMLの記述は下記のようになります。

   1:<TextBlock Grid.Row="0"
   2:            Grid.Column="2"
   3:            Style="{StaticResource ErrorStyle}"
   4:            Text="{Binding Customer.Errors[Name], 
   5:     Converter={StaticResource FirstErrorConverter}}" />

Textプロパティは、Customer.Errors[Name]にバインディングされます。この Customer.Errors[Name]は、名前プロパティに関連付けられたルールの評価に起因するエラー文字列のコレクションを返します。ただし、この文字列のコレクションを、テキストプロパティに設定するために、単純な文字列に変えるコンバーターが必要となります。そこで、Prism の AdventureWorksShopperサンプルから、エラーが存在する場合にコレクションのうち最初の部分を取得するコンバーターをコピーして、ここに実装します。

   1:using System;
   2:using System.Collections.Generic;
   3:using System.Linq;
   4:using Windows.UI.Xaml.Data;
   5:  
   6:namespace PrismAppValidation.Converters
   7: {
   8:/// <summary>
   9:/// Value converter that retrieves the first error of the collection, or null if empty.
  10:/// </summary>
  11:publicsealedclass FirstErrorConverter : IValueConverter
  12:     {
  13:publicobject Convert(objectvalue, Type targetType, object parameter, string language)
  14:         {
  15:             ICollection<string> errors = valueas ICollection<string>;
  16:return errors != null&& errors.Count > 0 ? errors.ElementAt(0) : null;
  17:         }
  18:  
  19:publicobject ConvertBack(objectvalue, Type targetType, object parameter, string language)
  20:         {
  21:thrownew NotImplementedException();
  22:         }
  23:     }
  24: }

ErrorStyleセットは、前景色を赤、フォント サイズを 20 に設定します。

妥当性検証エラーがある場合にコントロールをハイライトする

もう一つ、検証メカニズムでサポートする必要があるのは、検証エラーがあるときには、何らかの方法でコントロール自体を強調することです。しかし、WinRT の場合、そのために最初から用意されたものはないので、少し作業を行う必要があります。一番良いのは、既存のコントロールのビヘイビアに不十分な個所を補うために、コードを書くことです Win RT では、(アタッチドプロパティに基づく) アタッチドビヘイビアを使うことは、(Win RT の中で既に Blend ビヘイビアでサポートされている第1級のサポートがなされるまでは)極めて有効な方法です。

Prism のサンプル コードは、このために実装されているものです。ここでは、Validation QuickStartサンプルから、HighlightOnErrorsクラスをコピーして実装します。これは、TextBox とともに動作し、それの外観を変更するエラーがあるときに、スタイルを適用するように設計されているクラスです。

   1:using System.Collections.ObjectModel;
   2:using System.Linq;
   3:using Microsoft.Practices.Prism.StoreApps;
   4:using Windows.UI.Xaml;
   5:using Windows.UI.Xaml.Controls;
   6:  
   7:namespace PrismAppValidation.Behaviors
   8: {
   9:publicstaticclass HighlightOnErrors
  10:     {
  11:publicstatic DependencyProperty PropertyErrorsProperty =
  12:             DependencyProperty.RegisterAttached("PropertyErrors", typeof(ReadOnlyCollection<string>), typeof(HighlightOnErrors),
  13:new PropertyMetadata(BindableValidator.EmptyErrorsCollection, OnPropertyErrorsChanged));
  14:  
  15:publicstatic ReadOnlyCollection<string> GetPropertyErrors(DependencyObject obj)
  16:         {
  17:if (obj == null)
  18:             {
  19:returnnull;
  20:             }
  21:  
  22:return (ReadOnlyCollection<string>)obj.GetValue(PropertyErrorsProperty);
  23:         }
  24:  
  25:publicstaticvoid SetPropertyErrors(DependencyObject obj, ReadOnlyCollection<string> value)
  26:         {
  27:if (obj == null)
  28:             {
  29:return;
  30:             }
  31:  
  32:             obj.SetValue(PropertyErrorsProperty, value);
  33:         }
  34:  
  35:privatestaticvoid OnPropertyErrorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
  36:         {
  37:if (args == null || args.NewValue == null)
  38:             {
  39:return;
  40:             }
  41:  
  42:             TextBox textBox = (TextBox)d;
  43:             var propertyErrors = (ReadOnlyCollection<string>)args.NewValue;
  44:  
  45:             Style textBoxStyle = (propertyErrors.Count() > 0) ? (Style)Application.Current.Resources["HighlightTextStyle"] : null;
  46:  
  47:             textBox.Style = textBoxStyle;
  48:         }
  49:     }
  50: }

PropertyErrorsと呼ばれるアタッチドプロパティが宣言されています。これは、プロパティの Errorsコレクションへのバインディングを介して、そのプロパティを設定できるように設計されています。Errors コレクションが変更されたとき、そのロジックを再評価し、Errors コレクションにエラーがある場合には、コントロールにスタイルを設定します。そして、TextBox にそれを設定します。

   1:<TextBox Grid.Row="0"
   2:          Grid.Column="1"
   3:          Text="{Binding Customer.Name, Mode=TwoWay}"
   4:          Margin="5"
   5:          Width="500"
   6:          HorizontalAlignment="Left"
   7:          behv:HighlightOnErrors.PropertyErrors="{Binding Customer.Errors[Name]}" FontSize="24" />

名前、電話、および県のフィールドに(エラーが起こるように)適当な値を入れて、それらのフィールドで出るエラーを表示します。

image

注文送信ボタンを押した時にすべてのプロパティを検証する
最終的にやりたいことは、ユーザーが送信しようとしたときに、すべてのエラーがチェックされることです。なぜなら、ユーザーがデザインモードでフィールドを変更しようとしているときには検証エラーは表示されないためです。またいくつかの他の必要なフィールドがある場合があります。加えて、それらの妥当性検証が有効な場合、通常は、バックエンド側の呼び出しもせず、変更も保存しません。
そこで、SubmitOrderボタンにバインディングされた DelegateCommandを下記のコマンドハンドラーに記述します。
   1:privatevoid OnSubmit()
   2: {
   3:  
   4:     Customer.ValidateProperties();
   5:  
   6:     var allErrors = Customer.GetAllErrors().Values.SelectMany(c => c).ToList();
   7:  
   8:if (allErrors.Count == 0)
   9:     {
  10:  
  11:     }
  12: }

ValidateProperties()メソッドは、オブジェクト上のすべてのプロパティ間の検証をトリガーできます。これが完了すると、各プロパティのエラーコレクションが設定されます。GetAllErrorsメソッドは、すべてのプロパティの間ですべてのエラーを収集でき、LINQでリストにそのディクショナリ―を平坦化することができます。

まとめ

今回は、Prism の検証機能を使用して、最小限の労力で、豊富な入力検証サポート機能を実装する方法をご紹介しました。最初にやるべきなのは、ValidatableBindableBaseを継承した Modelオブジェクトを作ることと、プロパティセットのブロックから、ベースクラスにある SetPropertyをコールすることです。

以上です。もう少しコンパクトなサンプルと解説については、検証のクイック スタートのページをご参照ください。

それでは、また!今度は Build 後かなw?(^^;)ゞ

鈴木章太郎

 


Viewing all articles
Browse latest Browse all 29128

Latest Images

Trending Articles



Latest Images

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>