DOBON.NET DOBON.NETプログラミング掲示板過去ログ

UserControlのLabelクリックイベントが起きなくなる

環境/言語:[Windows7、C#.NET、Visual Studio 2008、.NET Framework 3.5]
分類:[.NET]

ユーザーコントロールにラベルを張り付け、そのラベルのクリックイベントでフォーカス遷移を起こしています。
上記ユーザーコントロールとテキストボックスをフォームに配置し、テキストボックスにValidatingイベントを用意しています。
テキストボックスにフォーカスがある状態で、ユーザコントロールのラベルクリックでフォーカス遷移を起こし、
発生したValidatingイベントにてe.Cancelをtrueにすると、それ以降ユーザーコントロールのラベルクリックイベントが発生しなくなります。
(ユーザーコントロールを介しなければ問題ないのですが…)

いろいろ調べてみましたが、何が原因なのかわからず…
こういった制限事項があるのか、プログラム上の問題なのか、ご教授ください。

ソース抜粋
・ユーザーコントロール側(ControlTest.cs)
public partial class ControlTest : UserControl
{
    public ControlTest()
    {
        InitializeComponent();
    }

    private void label1_Click(object sender, EventArgs e)
    {
        this.label1.Focus();
    }
}

・フォーム側(上記ControlTestとTextBoxを配置)
public partial class FormTest : Form
{
    public FormTest()
    {
        InitializeComponent();
    }

    private void textBox1_Validating(object sender, CancelEventArgs e)
    {
        e.Cancel = true;
    }
}

宜しくお願い致します。
■No31257に返信(きらさんの記事)

Validating で e.Cancel に true を指定したら、
Focus の遷移はしないかと思います。
単に TextBox から Focus が移動できていないだけではありませんか。

Validating って、そのためのイベントかと思います。
久しぶりの回答です。

ここ 2 年はコーディングはおろか、Windows 系の開発をしていないので、記憶での回答になります。

■No31257に返信(きらさんの記事)
> 発生したValidatingイベントにてe.Cancelをtrueにすると、それ以降ユーザーコントロールのラベルクリックイベントが発生しなくなります。
> (ユーザーコントロールを介しなければ問題ないのですが…)

この状況がどういう状況なのか少し整理してみましょう。

UserControl (コンテナ) に含まれている Active なコントロールは、Form のそれとは別に管理されていることはご存じでしょうか。またフォーカスが当たっている Control が ActiveControl とイコールとは限らないこともご存じでしょうか? また Leave や Validating 系のイベントがそれらを条件としていること、ゆえに Windows は ActiveControl を処理する際、該当するコントロールがないと以後のフォーカス制御でのイベントが正しく認知できないことはご存じでしょうか?

ちなみにその "ユーザーコントロール" には、フォーカスを '維持' できるコントロールが別で配置されていますか? label1 しかないというのであれば、モロに上記の事象に該当します。もちろん label2 や label3 があっても発生します。Label はフォーカスは '受け取れ' ますが、TextBox のようにフォーカスを '維持' できるコントロールではありませんので (標準コントロールですが、Label は少し特殊なコントロールだったりします)。

もし 「そんな状況になってないよ」 ということであれば、上記は勇み足回答です。とはいえ、コンテナのフォーカス不整合の可能性は高いとみています。ですので、原因特定のために、UserControl と Form 双方で、UpdateDefaultButton メソッドをオーバーライドし、ActiveControl が何であるのか、また Focused しているコントロールが何であるかを実際にデバッグ出力して、原因の切り分けから行いたいところです。

# UpdateDefaultButton メソッドは .NET Framework インフラ用のメソッドですので普段は使用しないようにしてください。
■No31261に返信(Rukuさんの記事)
> ■No31257に返信(きらさんの記事)
>
> Validating で e.Cancel に true を指定したら、
> Focus の遷移はしないかと思います。
> 単に TextBox から Focus が移動できていないだけではありませんか。
>
> Validating って、そのためのイベントかと思います。

ご回答ありがとうございます。

Validatingキャンセル時にフォーカスの遷移が起こらないのは、意図している動作で問題ないです。
うまく説明が難しいのですが、TextBoxのValidatingをキャンセルすると、それ以降どのコントロールにフォーカスがあっても、UserControl上のラベルに設定したクリックイベントが起きなくなってしまう現象が発生し、その原因がよくわからない、といった状況になります。

うまく説明できず済みません…
■No31268に返信(じゃんぬねっとさんの記事)
> UserControl (コンテナ) に含まれている Active なコントロールは、Form のそれとは別に管理されていることはご存じでしょうか。またフォーカスが当たっている Control が ActiveControl とイコールとは限らないこともご存じでしょうか? また Leave や Validating 系のイベントがそれらを条件としていること、ゆえに Windows は ActiveControl を処理する際、該当するコントロールがないと以後のフォーカス制御でのイベントが正しく認知できないことはご存じでしょうか?
>
> ちなみにその "ユーザーコントロール" には、フォーカスを '維持' できるコントロールが別で配置されていますか? label1 しかないというのであれば、モロに上記の事象に該当します。もちろん label2 や label3 があっても発生します。Label はフォーカスは '受け取れ' ますが、TextBox のようにフォーカスを '維持' できるコントロールではありませんので (標準コントロールですが、Label は少し特殊なコントロールだったりします)。
>
> もし 「そんな状況になってないよ」 ということであれば、上記は勇み足回答です。とはいえ、コンテナのフォーカス不整合の可能性は高いとみています。ですので、原因特定のために、UserControl と Form 双方で、UpdateDefaultButton メソッドをオーバーライドし、ActiveControl が何であるのか、また Focused しているコントロールが何であるかを実際にデバッグ出力して、原因の切り分けから行いたいところです。

ご回答ありがとうございます。

ActiveControl関連の動作について、ご指摘の内容は概ね理解しているつもりです。
記載した現象発生時は確かにラベルコントロールしかUserControlに配置していなかったのですが、テスト用にボタンやテキストボックスをUserControl上に配置しても現象は変わりませんでした。

UpdateDefaultButtonで確認してみたところ、以下のように動いていました。
・ユーザコントロールのラベルクリックイベント(label1_Click)
label1.Focused = False
this.ActiveControl =
※このあとでFocusを自身(ラベル)にセット(this.label1.Focus();)

・Form上のTextBoxのValidatingイベント発生(textBox1_Validating)
textBox1.Focused = False
controlTest1.Focused = False
form.ActiveControl = controlTest1
ここでイベントキャンセル(e.Cancel = true;)

・FormのUpdateDefaultButtonメソッドが呼ばれる
textBox1.Focused = False
controlTest1.Focused = False
form.ActiveControl = textBox1

・ユーザコントロールのラベルクリックイベント(label1_Click)Focus()後の続き
label1.Focused = False
userControl.ActiveControl =

フォーカスの遷移自体に特におかしなところがあるようには見えず、なぜValidatingをキャンセルしたときのみ、ラベルのClickイベントが発生しなくなるのかは分かりませんでした…
・追記です
UserControl上にさらにTextBoxを加え(ラベルとテキストボックスがある状態)、
ユーザコントロール上のテキストボックスをクリックして(Focusメソッドは使用せず)、普通にForm上のTextBoxのValidatingを起こしてそのイベントをキャンセルしても、UserControl上のラベルのクリックイベントが発生しなくなりました。
■No31273に返信(きらさんの記事)

Cancel時に
textBox1をActiveControlに設定しなおすのは駄目ですか?
■No31274に返信(shuさんの記事)
> ■No31273に返信(きらさんの記事)
>
> Cancel時に
> textBox1をActiveControlに設定しなおすのは駄目ですか?

ご指摘頂いたように、Validatingのキャンセル時にActiveControlを再設定することで解決いたしました。
ありがとうございます!

Rukuさま、じゃんぬねっとさま、ご指摘の内容を理解できずすみませんでした。
解決済み!
2013/01/31(Thu) 11:36:07 編集(投稿者)

# 解決済みのチェックを外してしまったので編集投稿...

どうやら勝手に前提を作り出して、誤った回答をしてしまったみたいですね。ふざけた回答をして申し訳ございません。ちょっと心配な点もあるので、訂正しつつ回答をさせて頂きます。

最初に見た時には気付かなかったのですが、明示的に Focus の設定をしているのは、UserControl のどこをクリックしても UserControl がフォーカスを得るようにしたいという目的があるみたいですね (通常 Label を選択してもフォーカスが変化しないので)。

# これも勝手な前提だったらどうしよw

原因についてですが、Validating で Cancel していながら、その前に別管理コンテナでのフォーカスを設定しているからですね (といっても Label の Click イベントという組み合わせでしか起きないと思いますが)。

Validating イベントが起きている最中はフォーカスの制御は、イベント引数の Cancel プロパティ以外では原則してはなりません。どうしても自前でフォーカスの再設定をする場合は、AutoValidate を Disable などに変更しなければならないという知られざるお作法があります。この方法の良いところは、検証 OK の場合のみ Validated イベントが発生して動作の切り分けがしやすい点です。というより Validated イベントの有効活用ってそれしかないわけですがw

# 1.1 以前はこれがなかったので Validating イベントは限られた場面でしか使い道がありませんでした。

で、今回の例で「お作法に則って実装する」ならば、コンテナ外 (この場合は Form) の Validate メソッドでマニュアル検証して、検証結果 (戻り値) が NG であれば何もしない。OK であれば初めて Focus を制御するという流れでなければなりません。

...ですが、目的と勘案するとちょっと大げさというか面倒ですよね。本投稿の冒頭で書いた 「目的」 が正しいのであれば、内部 Focus 記録に依存しない MouseDown イベントを使う方法があります。というより、本来の目的からすると Click ではなく MouseDown の方が Windows 標準 (*1) の動作でフォーカス遷移するのでこちらの方がよいでしょう。

*1 ・・・ Windows のフォーカス遷移はマウスのボタンを Down した時点で起きる。またどのマウス ボタンでも起きる。

[蛇足]
もっと複雑なコンテナで Validating イベントで無理にフォーカス制御すると、いよいよ行方不明になることがあります。Grid 系のコントロールを自作すると、仮想コントロールを使ったりしますが、お作法を知らずに実装すると起きがちですね。この場合は素直に Leave イベントを使うか、マニュアルで Validate メソッドを呼びます。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板