Leave イベントの発生原因は取得可能でしょうか?

タグの編集
投稿者 hori  (社会人) 投稿日時 2021/11/1 17:40:26
Form に TextBox 2つと Button を1つ配置して付きのようなコードを書きました。

=================================================================

Public Class Form1

    Dim flg As Integer = 0

    Private Sub TextBox1_Enter(sender As Object, e As EventArgs) Handles TextBox1.Enter

        flg = 1

        TextBox1.Text = "3"

    End Sub

    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown

        If e.KeyData = Keys.Enter Then

            TextBox2.Text = "4"

            TextBox2.Select()

        End If

    End Sub

    Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave

        If flg = 1 Then

            MsgBox("tbx")

            flg = 2

        End If

    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        flg = 5

        MsgBox("btn")

    End Sub

    Private Sub TextBox2_Enter(sender As Object, e As EventArgs) Handles TextBox2.Enter

        TextBox2.Text = CStr(flg)

    End Sub

    Private Sub TextBox2_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox2.KeyDown

        If e.KeyData = Keys.Enter Then

            flg = Val(TextBox2.Text)

        End If

    End Sub

End Class

===================================================================

いろいろやってみて判ったことは、
①TextBox1 にフォーカスがある時、Button1 をクリックしたら TextBox1.Leave が発生し、Button1.Click は無視される。(MsgBox("btn") が表示されない。)
②TextBox1 にフォーカスがある時、TextBox2 をクリックしたら TextBox1.Leave が発生し、その後、TextBox2.Enter が発生する。("tbx" が表示され、flg = 2 になる)

と云う事です。①の場合、僕の感覚ではボタンをクリックしたから Leave が発生するのだから、ボタン・クリックが先でテキスト1・リープが後であるべきだと思うのですが・・・・

ともかく、①の状況でボタン・クリックをしたとき、"tbx" を表示させずに、"btn" を表示させる。けれど、②の状況では現状維持と云う方法はありますか?。

そのためには、Leave の発生原因を取得できれば一番いいのですが、僕の調べた限り無いようなのです。が、本当はあるんじゃないかと思って質問しました。

次善策としては、上記の例なら、TextBox2.Enter 内に、MsgBox("tbx") を移動させて、TextBox1.Leave を削除する事かなと思うのですが、これを実行する前に本当に無いのかどうか確かめたいのです。

ご存知の方、居られましたらよろしくご教示ください。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/11/1 18:55:59
Leave や Enter のイベント発生順はこのあたり。
http://rucio.o.oo7.jp/main/dotnet/shokyu/standard23.htm


> MsgBox("tbx")
フォーカス処理系のイベントや、マウス・キーボード入力系のイベントを扱う場合、
その途中で MsgBox や MessageBox を呼び出すのは、あまり良い方法ではありません。

本来のイベント処理に対して割り込まれる形になるので、本来発生するイベントが
処理されなくなるなどの問題を生じる事があります。
https://atmarkit.itmedia.co.jp/bbs/phpBB/viewtopic.php?topic=28938&forum=7

「Validating / Validated で代用できないかを検討する」、あるいは
「そもそもフォーカスの移動順に依存しないような画面設計にする」ことをお奨めします。

もしくは、Idle イベントや BeginInvoke メソッドなどを用いて、
「イベント処理が終わった後のアイドル時にメッセージを表示する」ようにします。


なお、この掲示板にプログラムコードを貼るときは、
下記 1 のコードタグを使うと読みやすくなりますよ。
http://rucio.cloudapp.net/Usage.aspx
投稿者 hori  (社会人) 投稿日時 2021/11/1 20:51:26
魔界の仮面弁士さま。ご回答ありがとうございました。

BeginInvoke なるヒントをいただきまして、これから辿って下記のコードに至りました。

一応、僕の意図通りに動いているようですが、ダメなところがあればご指摘ください。

なお、最初のコードでボタン・クリックが無視されていた部分は、ご指摘の通り MsgBox が原因でした。
ちなみに、下記のコードでは影響ないようです。ありがとうございました。


Public Class Form1

    Dim flg As Integer = 0

    Dim ms As String = "10"

    Private Sub TextBox1_Enter(sender As Object, e As EventArgs) Handles TextBox1.Enter

        flg = 1

        ms = "t15"

        TextBox1.Text = "3"

    End Sub

    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown

        If e.KeyData = Keys.Enter Then

            TextBox2.Text = "4"

            InvokeMethod()

            TextBox2.Select()

        End If

    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        flg = 5

        ms = "18"

        MsgBox(ms)

    End Sub

    Private Sub TextBox2_Enter(sender As Object, e As EventArgs) Handles TextBox2.Enter

        TextBox2.Text = ms

    End Sub

    Private Sub TextBox2_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox2.KeyDown

        If e.KeyData = Keys.Enter Then

            flg = Val(TextBox2.Text)

        End If

    End Sub

    Delegate Sub InvokeDelegate()

    Private Sub TextBox1_Leave(sender As Object, e As EventArgs)

        TextBox1.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))

    End Sub

    Public Sub InvokeMethod()

        If flg = 1 Then

            MsgBox("tbx")

            ms = "20"

            MsgBox(ms)

            flg = 2

        End If

    End Sub

End Class

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/11/1 21:16:09
> BeginInvoke なるヒントをいただきまして、
処理中にウィンドウが閉じられた場合、処理タイミングによっては
ObjectDisposedException 例外が発生するのでご注意ください。


> Delegate Sub InvokeDelegate()

.NET Framework 3.5 以降には Action デリゲートがあるので、
わざわざデリゲートを用意する必要は無いですよ。
引数付きなら Action(Of ) デリゲート。

それとInvoke / BeginInvoke メソッドに渡すデリゲートは、自作デリゲートや Action デリゲートではなく、
MethodInvoker デリゲートを使うことが望ましいです。シグネチャに互換性があれば
MethodInvoker 以外でも呼べるのですが、内部的には MethodInvoker の方が効率が良いです。

Control.Invoke(Delegate, Object() ) メソッドのヘルプより引用
>
> デリゲートは、EventHandler のインスタンスである場合もあります。その場合は、
> sender パラメータにこのコントロールが含まれ、イベント パラメーターには
> EventArgs.Empty が含まれます。デリゲートは MethodInvoker のインスタンス、または
> 空のパラメータ リストをとるその他のデリゲートである場合もあります。
> EventHandler デリゲートまたは MethodInvoker デリゲートの呼び出しの方が、
> 別の種類のデリゲートの呼び出しよりも高速に実行されます。 
>
投稿者 hori  (社会人) 投稿日時 2021/11/3 10:32:39
その後、今書いているプログラムに入れてみたのですが

    Delegate Sub InvokeDelegate()

    Private Sub TextBox1_Leave(sender As Object, e As EventArgs)

        TextBox1.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))

    End Sub


は、有っても無くても同じでした。

てっきり、TextBox1.Leave の発生を次のイベント発生後まで待機させているのだと思い込んでいましたが、TextBox2.に Enter したとき動かないのでそうではないみたいです。
考えてみれば、全てのイベントに flg の変更を書き込まなければならなくなるので馬鹿気た話ですね。

と云う事で、話は振出しに戻ってしまいました。

Validating や Validated も当たってみましたが、そのイベントの発生原因を通知する関数は無いようなので適さないようです。

で、いろいろ試した結果、MouseEnter を使えそうなので以下のようにしました。

Public Class Form1

    Dim ms As String = "on"

    Private Sub TextBox1_Enter(sender As Object, e As EventArgs) Handles TextBox1.Enter

        Label1.Text = ""

        TextBox1.Text = ms

        ms = "on"

    End Sub

    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown

        If e.KeyData = Keys.Enter Then

            TextBox2.Select()

        End If

    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        TextBox2.Text = "bc : " & ms

        ms = "BC"

        TextBox1.Text = ms & Now.ToString("ssfffff")

    End Sub

    Private Sub TextBox2_Enter(sender As Object, e As EventArgs) Handles TextBox2.Enter

        TextBox2.Text = ms

    End Sub

    Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave

        WWW()

    End Sub

    Private Sub TextBox2_MouseEnter(sender As Object, e As EventArgs) Handles TextBox2.MouseEnter

        ms = "on"

    End Sub

    Private Sub Button1_MouseEnter(sender As Object, e As EventArgs) Handles Button1.MouseEnter

        ms = "off"

    End Sub

    Private Sub WWW()

        Label1.Text = ms & Now.ToString("ssfffff")

    End Sub

End Class

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/11/4 09:52:35
> TextBox1.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))
Form1 と TextBox1 は通常、同じスレッドに属する物ですので、
TextBox1.BeginInvoke は、Me.BeginInvoke や BeginInvoke と書いても同じです。


> 次のイベント発生後まで待機させているのだと思い込んでいましたが、
BeginInvoke は、アイドル時(つまり、メッセージが空になったとき)に呼び出されることになります。

なので提示頂いたケースだと、「次のイベント発生後まで待機」するのではなく、
実質的には「現在のイベントの End Sub 通過直後」に呼ばれことになるでしょう。

たとえば下記の場合、Label1/2 は "abc" ですが、Label3 は "acb"、Label4 は "acbd" となります。
Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Label1.Text = "a"
        Call Sub() Label1.Text &= "b"
        Label1.Text &= "c"
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Label2.Text = "a"
        Invoke(Sub() Label2.Text &= "b")
        Label2.Text &= "c"
    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        Label3.Text = "a"
        BeginInvoke(Sub() Label3.Text &= "b")
        Label3.Text &= "c"
    End Sub

    Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
        Label4.Text = "a"
        BeginInvoke(Sub() Label4.Text &= "b")
        Label4.Text &= "c"
        Application.DoEvents()
        Label4.Text &= "d"
    End Sub
End Class



> Validating や Validated も当たってみましたが、そのイベントの発生原因を通知する関数は無いようなので適さないようです。

CausesValidation プロパティおよび Validating / Validated イベントは、
遷移元をトレースするという使い方ではなく、e.Cancel 引数と組み合わせて
入力検査を設けるための補助機構です。

フォーカス遷移順そのものを捉えたいなら、Enter/Leave イベントをきちんと管理すれば済むはずです。
統括的に捉えたい場合は、下記の じゃんぬねっと さんの記事を参考にしてみてください。
http://blogs.wankuma.com/jeanne/archive/2007/04/02/69804.aspx