[C#][WPF]ViewModel側からコントロールのフォーカスを指定する方法

C#
記事内に広告が含まれています。

アプリケーションを作成する際、使う人を意識していますか?

例えば、

入力チェックなどを行い、エラーのある項目を表示した後、その項目のコントロールにフォーカスを当てておくと、すぐに入力・修正することが出来ます。

他にも、

登録ボタンを押して登録した後、画面をクリアして再入力できるようにする場合、フォーカスも移動してあると、すぐに入力することが出来ます。

ユーザ視点に立った場合、とても重要なことですよね!?

MVVMパターンで製造していると、処理ロジックは、ViewModelもしくはModel側に実装することになりますので、入力チェックの結果をView側にどうやって返すかが問題となってきます。

今回はMVVMでフォーカスを指定する方法を紹介します。

View内でフォーカスを指定する

まずは、View内でフォーカスを指定する方法を見ていきましょう。

さっそくコードを示します。

<Window
    FocusManager.FocusedElement="{Binding ElementName=TextBox2}">

    <TextBox Name="TextBox1"/>
    <TextBox Name="TextBox2"/>
    <TextBox Name="TextBox3"/>
</Window>

マーカーした箇所がフォーカスを指定するプロパティです。

Windowなどのコントロールで使うのが基本ですが、DataTriggerでも指定できるので、下のように使うことも出来ます。

<Window>
    <TextBox Name="TextBox1">
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsText1Focus}" Value="True">
                    <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBox1}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox>
    <TextBox Name="TextBox2"/>
    <TextBox Name="TextBox3"/>
</Window>

ViewModelから結果を返す

さきほど、DataTriggerを使ってフォーカスする方法を伝えましたが、DataTriggerにViewModelのプロパティをバインドすれば、ViewModelからフォーカスを指定することができます。

<Window>
    <TextBox Name="TextBox1">
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsText1Focus}" Value="True">
                    <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBox1}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox>
    <TextBox Name="TextBox2"/>
    <TextBox Name="TextBox3"/>
</Window>

入力チェックでエラー箇所を強調表示する場合には、プロパティの型をenumで定義すると便利です。

<Window>
    <TextBox Name="TextBox1">
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsError}" Value="ErrText1">
                    <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBox1}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox>
    <TextBox Name="TextBox2">
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsError}" Value="ErrText2">
                    <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBox2}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox>
    <TextBox Name="TextBox3">
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsError}" Value="ErrText3">
                    <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TextBox3}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox>
</Window>

フォーカスは設定した瞬間に移動する

フォーカスは、設定した瞬間に移動するという特徴があります。

通常はそれでも問題ありません。しかし、入念に作りこまれたプログラムだと入力制限や使用可否の制御などが行われ、瞬間のフォーカス移動だと移動できない場合が出てきます。

例えば、コンボボックスの選択肢により、入力できる項目が変化するような場面です。

コンボボックスの選択肢が変化したことにより、TextBoxのIsEnabledを変化させている間にフォーカス移動が行われ、実際の動作ではフォーカスが移動していないということが起こります。

数msの間の出来事なんですけどね。

遅延実行でフォーカスを設定する

対処方法としては、フォーカス移動を遅らせてやります(遅延実行)。

遅延実行させるには、InvokeAsyncメソッドを使います。

書き方は、こんな感じ。

MainWindow.Dispatcher.InvokeAsync(() =>
{
    textBox1.Focus();
};

InvokeAsyncメソッドを使うことにより、ディスパッチャのイベントキューで実行が保留されるため、TextBoxのIsEnabledが変化し終わった後で、フォーカスが設定されるようになります。

注意

全てをInvokeAsyncメソッドのコールバックに記載してしまっては、遅延実行の意味がなくなってしまいますので、遅延させたい処理だけを記載するようにしましょう。

最後に

すべてのひとがマウスで操作するとは思わない方が良いです。

私の場合、テンキーだけで入力する人たちに遭遇しました。まさに未知との遭遇。

全てのデータが数字だけで入力されていくのです。数量や金額はもちろん、商品もコード値で入力されていきます。

テンキーってキーボードの右側に付いていることが多いですよね!?
マウスも右利きの方は、基本的に右手で操作します。

なので、テンキーからマウスに手を移動して操作する時間が無駄なんだそうです。

傍目から見た私からしても、いちいちフォーカスをマウスで移動させていては作業効率が悪化するのは明白でした。

少しでも使用者の立場になってプログラミングすることが重要だと思い知らされた経験でした。

少しでも参考になれば幸いです。

コメント

タイトルとURLをコピーしました