ぼやきごと/2013-03-24/C#:コントロールの Enter イベント内で更にフォーカス移動したい場合は BeginInvoke メソッドを使う のバックアップ(No.1)


C#:コントロールの Enter イベント内で更にフォーカス移動したい場合は BeginInvoke メソッドを使う

色々なGUIプログラムを作っていると、「あるコントロールがフォーカスされた時、他のコントロールにフォーカスを移す」という処理が必要になったりします。
必要になるんです。なるということにしてください。

最初にフォーカスを受け取るコントロールを ctrlFirst 、フォーカス移動先のコントロールを ctrlTarget とすると、 ctrlFirstEnter イベントで次のように書けばいい気がします。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
-
-
!
!
private void ctrlFirst_Enter(object sender, EventArgs e)
{
    // ctrlTarget にフォーカスを移す
    ctrlTarget.Focus();
}

単純にフォーカスが移ればいいだけの場合、大抵はこれでうまくいきます。

しかし、 ctrlFirstctrlTarget が別のコントロールグループ(Panel 等)に属しており、なおかつ Label のニーモニックキー(Alt+A 等の入力でフォーカス移動する機能)を使った場合にうまくフォーカス移動してくれません。(他にもうまくフォーカス移動しないケースがあるかもしれません)
また、 Leave イベントが複数回呼ばれていたり、 Tab でフォーカス移動すると Validating イベントが呼ばれず入力検証を素通りしてしまったりと、色々おかしな挙動が発生します。
Enter イベントが呼び出されるのはまさにフォーカス移動を処理している最中であり、そこに割り込みでフォーカス移動しようとしているためにおかしくなっているのではないかと推測されます。(明確な根拠無し)

結論: Enter イベント内で直接他のコントロールにフォーカスを移してはいけません。

…じゃあどうすればいいのか?

Enter イベント内で直接処理しないようにすればいい、ってことですね。
コントロールの Control.BeginInvoke メソッドを使って、後から実行してもらうようにお願いしましょう。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
 
-
-
!
!
private void ctrlFirst_Enter(object sender, EventArgs e)
{
    // ctrlFirst さん、手が空いたらこれ呼んでくださいね
    ctrlFirst.BeginInvoke(new Action(() => ctrlTarget.Focus()));
}

Control.BeginInvoke メソッドは Win32 API でいうところの PostMessage 関数みたいなものです。*1
これを同一スレッドから呼び出すことでコントロールに対して遅延実行的なことが行えます。

Control.BeginInvoke メソッドといえば「別スレッドからのUI制御で使われるもの」というイメージですが、同一スレッド内で活用できることもあるんだよ、というお話でした。

Category: [プログラミング][C#] - 2013-03-24 21:28:48

*1 実際にどういう仕組みで実装されているかまでは知りませんが…。