先日書いた画面が表示された直後に入力値の妥当性検証を行い画面にフィードバックをする方法ですが、MSDNフォーラムの元質問者の方からコメントをいただけました。
私の方で悩んでいた部分としては、初期化時にエラーにしたいのではなく、「画面初期化時はエラーの値が入っていてもエラーにしない」という処理を実装しようとしております。 現状、一応それらしい動きにはなってきているのですが、「エラー値&エラー表示無し」のコントロールにフォーカスを当てて、値を変更せずにLostFocusした時に、エラーを表示するという部分が突破できていません。
要件を整理すると
- 初期表示では入力にエラーがあってもエラー表示はしない
- TextBoxのでフォーカスが外れたタイミングで検証を行わせたい
- 入力内容を変更してない状態でもフォーカスが外れたタイミングで検証を行わせたい
ということになると思います。
先日のサンプルをちょっと変えて試してみます。まず、初期状態で妥当性検証をする必要は無いのでSizeChangedでバリデーションを行う必要は無くなるのでCallMethodActionは無くします。この状態で、要件のうち1と2は満たせてますが3が満たせていません。これは、おそらくTextBoxが気を利かせてくれて内容を変更してないときにはフォーカスが外れたタイミングでViewModelに値を設定してくれないおかげだと思います。
なので、解決方法としてはTextBoxのTextプロパティの値を強引にViewModelに反映させるTriggerActionを作ってEventTriggerでLoatFocus時にActionを実行するように構成します。
まず、TextBoxのTextプロパティの値を強制的にViewModel側は反映するTriggerActionを作成します。
namespace PrismMVVMSample.Behaviors { using System.Windows.Controls; using System.Windows.Data; using System.Windows.Interactivity; /// <summary> /// TextBoxのTextPropertyへBindingしてるSourceへ値をUpdateするアクション /// </summary> public class TextBoxUpdateSourceAction : TriggerAction<TextBox> { /// <summary> /// TextPropertyへのBinding /// </summary> private BindingExpression bindingExpression; protected override void OnAttached() { base.OnAttached(); // TextPropertyへのBindingを取得 this.bindingExpression = this.AssociatedObject.GetBindingExpression( TextBox.TextProperty); } protected override void OnDetaching() { this.bindingExpression = null; base.OnDetaching(); } protected override void Invoke(object parameter) { // BindingされてたらSourceの値を更新 if (this.bindingExpression == null) { return; } this.bindingExpression.UpdateSource(); } } }
そして、このTriggerActionをTextBoxに設定します。
<!-- この名前空間を上で定義しておく xmlns:PrismMVVMSample_Behaviors="clr-namespace:PrismMVVMSample.Behaviors" --> <TextBox x:Name="textBox" Margin="68,8,0,0" TextWrapping="Wrap" Text="{Binding Input, Mode=TwoWay, NotifyOnValidationError=True, UpdateSourceTrigger=Default}" VerticalAlignment="Top" TabIndex="0" HorizontalAlignment="Left" Width="236"> <!-- TextBoxのプロパティの変更があったときにBindingをUpdateする --> <i:Interaction.Behaviors> <Custom:UpdateTextBindingOnPropertyChanged/> </i:Interaction.Behaviors> <i:Interaction.Triggers> <!-- LostFocus時にBindingをUpdateする --> <i:EventTrigger EventName="LostFocus"> <PrismMVVMSample_Behaviors:TextBoxUpdateSourceAction/> </i:EventTrigger> </i:Interaction.Triggers> </TextBox> <TextBox x:Name="textBox2" Margin="68,38,0,0" TextWrapping="Wrap" Text="{Binding Input2, Mode=TwoWay, NotifyOnValidationError=True, UpdateSourceTrigger=Default}" VerticalAlignment="Top" TabIndex="0" HorizontalAlignment="Left" Width="236"> <i:Interaction.Triggers> <!-- LostFocus時にBindingをUpdateする --> <i:EventTrigger EventName="LostFocus"> <PrismMVVMSample_Behaviors:TextBoxUpdateSourceAction/> </i:EventTrigger> </i:Interaction.Triggers> <!-- TextBoxのプロパティの変更があったときにBindingをUpdateする --> <i:Interaction.Behaviors> <Custom:UpdateTextBindingOnPropertyChanged/> </i:Interaction.Behaviors> </TextBox>
ついでに、MainViewModelのInvalidStateAndAlertメソッドでHasErrorsをチェックする前にプロパティの妥当性検証を行うようにしました。前回は、必ず妥当性検証が行われていましたが、今回は妥当性検証が一度も行われない状態でボタンが押される可能性があるということに対処するためです。
private bool InvalidStateAndAlert() { // HasErrorsのチェックの前にプロパティの検証を行う this.ValidateProperties(); if (this.HasErrors) { this.AlertRequest.Raise(new Notification { Title = "情報", Content = "入力にエラーがあります" }); return true; } return false; }