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


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

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

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

#code(csharp){{
private void ctrlFirst_Enter(object sender, EventArgs e)
{
    // ctrlTarget にフォーカスを移す
    ctrlTarget.Focus();
}
}}

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

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

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

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

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

#code(csharp){{
private void ctrlFirst_Enter(object sender, EventArgs e)
{
    // ctrlFirst さん、手が空いたらこれ呼んでくださいね
    ctrlFirst.BeginInvoke(new Action(() => ctrlTarget.Focus()));
    ctrlFirst.BeginInvoke(new Func<bool>(ctrlTarget.Focus));
}
}}

@code{Control.BeginInvoke}; メソッドは Win32 API でいうところの @code{PostMessage}; 関数みたいなものです。((実際にどういう仕組みで実装されているかまでは知りませんが…。))~
これを同一スレッドから呼び出すことでコントロールに対して遅延実行的なことが行えます。

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

RIGHT:Category: &#x5b;[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]&#x5d;&#x5b;[[C#>ぼやきごと/カテゴリ/C#]]&#x5d; - 2013-03-24 21:28:48
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()