かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

TextBoxで入力内容を変更してなくても検証処理を走らせたい

先日書いた画面が表示された直後に入力値の​妥当性検証を行い画面にフィード​バックをする方法ですが、MSDNフォーラムの元質問者の方からコメントをいただけました。

私の方で悩んでいた部分としては、初期化時にエラーにしたいのではなく、「画面初期化時はエラーの値が入っていてもエラーにしない」という処理を実装しようとしております。

現状、一応それらしい動きにはなってきているのですが、「エラー値&エラー表示無し」のコントロールにフォーカスを当てて、値を変更せずにLostFocusした時に、エラーを表示するという部分が突破できていません。

要件を整理すると

  1. 初期表示では入力にエラーがあってもエラー表示はしない
  2. TextBoxのでフォーカスが外れたタイミングで検証を行わせたい
  3. 入力内容を変更してない状態でもフォーカスが外れたタイミングで検証を行わせたい

ということになると思います。

先日のサンプルをちょっと変えて試してみます。まず、初期状態で妥当性検証をする必要は無いので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;
}

実行してみよう

実行して動作を確認してみます。まずは実行直後、今回は前回と違って起動直後に妥当性検証が走らないのですっきりした見た目です。

入力1にフォーカスをあてて何も入力せずにフォーカスを外すと検証エラーが表示されます。

この状態でボタンを押すと入力1と入力2の両方に検証エラーが表示されて、入力内容にエラーがある旨のメッセージが表示されます。

もちろん、TextBoxにちゃんと値を入れている場合は、ちゃんと動きます。

プロジェクトのダウンロード

以下からダウンロードできます。