TableLayoutPanelのMouseLeaveについて

タグの編集
投稿者 レモネード  (その他) 投稿日時 2022/9/7 19:47:43
VB.NETのWindowsフォームアプリケーションです。

TableLayoutPanel1の中にButton1が配置されているとします。

TableLayoutPanel1の中にマウスカーソルが入ったときにイベントが欲しいのですが、
Button1上にマウスカーソルが来たときに
TableLayoutPanel1_MouseLeave
Button1_MouseEnter
の2つが呼ばれてしまいます。

TableLayoutPanel1内の子コントロール全てのMouseEnterをAddHandlerすることでMouseEnterはそれっぽく実現できましたが、
今度はMouseLeaveの方がどうすれば良いのか分かりません。

何か良い方法がありましたらお教えいただきたいです。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/9/7 21:34:34
代替策として、Timer 等で継続的にマウス位置を取得し、
TableLayoutPanel1の内外に移動したかどうかを、その都度判定するとか。

Private Sub Form1_Panel1MouseEnter(sender As Object, e As EventArgs) Handles Me.Panel1MouseEnter

End Sub

Private Sub Form1_Panel1MouseLeave(sender As Object, e As EventArgs) Handles Me.Panel1MouseLeave

End Sub


Private Event Panel1MouseEnter As EventHandler
Private Event Panel1MouseLeave As EventHandler
Private inside As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Timer1.Interval = 50
    Timer1.Start()
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    Dim p = MousePosition
    Dim cp = TableLayoutPanel1.Parent.PointToClient(p)
    If TableLayoutPanel1.Bounds.Contains(cp) Then
        If Not inside Then
            inside = True
            RaiseEvent Panel1MouseEnter(TableLayoutPanel1, EventArgs.Empty)
        End If
    Else
        If inside Then
            inside = False
            RaiseEvent Panel1MouseLeave(TableLayoutPanel1, EventArgs.Empty)
        End If
    End If
End Sub


なお上記は、TableLayoutPanel1 の一部が他のコントロールなどによって
隠されていた場合のことは考慮されていません。


> MouseEnterはそれっぽく実現できましたが
その方法をとる場合、もしも Button1 の周囲に隙間があれば、
Button1_MouseEnter と共に TableLayoutPanel1_MouseEnter が発生することになります。
場合によっては、その分の補正も必要になるかもしれませんね。


> TableLayoutPanel1の中にマウスカーソルが入ったときにイベントが欲しいのですが、
> Button1上にマウスカーソルが来たときに
> TableLayoutPanel1_MouseLeave
> Button1_MouseEnter
> の2つが呼ばれてしまいます。

マウス操作は基本的に、最も手前にあるウィンドウによってキャプチャされる仕様です。
ただしドラッグ操作の場合は、ドラッグを開始したウィンドウによってキャプチャされます。
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.capture

Button クラスの WndProc メソッド(≠Form クラスの WndProc ではありません)を
オーバーライドすれば、マウス操作を透過させて背面のウィンドウに
キャプチャさせることもできます。

この方法をとれば、マウス操作を常に親コントロール側のイベントでとらえられますし、
タイマー等で監視する必要もなくなります。

Protected Overrides Sub WndProc(ByRef m As Message)
    Const WM_NCHITTEST As Integer = &H84
    Const HTTRANSPARENT As Integer = -1
    If m.Msg = WM_NCHITTEST Then
        m.Result = New IntPtr(HTTRANSPARENT)
    Else
        MyBase.WndProc(m)
    End If
End Sub


ただしマウス操作が透過するということは、そのボタンをクリックすることさえも
できなくなるという事を意味します。(キーボードで操作することは可能ですが)

そのため、親となる TableLayoutPanel 側でクリックを受け取って
代わりに処理するといった対策が必要になるでしょう。

Private Sub TableLayoutPanel1_MouseClick(sender As Object, e As MouseEventArgs) Handles TableLayoutPanel1.MouseClick
    Dim b = TryCast(TableLayoutPanel1.GetChildAtPoint(e.Location), Button)
    If b Is Button1 AndAlso b.Visible AndAlso b.Enabled Then
        If Not b.Focused Then
            b.Focus()
        End If
        b.PerformClick()
    End If
End Sub
投稿者 レモネード  (その他) 投稿日時 2022/9/8 19:34:01
魔界の仮面弁士様、返信ありがとうございます。

流石にタイマーでループしてまでとは思っていなかったのですが、
タイマーが出てくるということはそれくらいしか方法が無いのですね...。

WndProcの発想はありませんでした、ありがとうございます。
それなら素直に作れそうですが、Button等のマウス処理を自前で作るとなると、
TextBoxなどが難しくなってしまいますし、
TableLayoutPanelを使うこと自体が間違ってるような気がしてきてしまいますね...。

>> MouseEnterはそれっぽく実現できましたが
はい、MouseLeaveがきちんと判定出来る前提ですので、ダメでした。言葉足らずですみません。

中々難しいですね...。
今躓いてるものは、TableLayoutPanelにLabelとButtonのみが10個程入ってるだけなので、
教えていただいた、タイマーかWndProcのやり方で、とりあえずごまかしちゃおうと思います。

早急な返信で、かつ分かりやすい解答で、ありがとうございました。
投稿者 (削除されました)  () 投稿日時 2022/9/8 22:04:12
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/9/8 22:05:24
誤記発見orz

> その方法をとる場合、もしも Button1 の周囲に隙間があれば、
> Button1_MouseEnter と共に TableLayoutPanel1_MouseEnter が発生することになります。

下記のように読み替えてください。両方を混ぜて書いてしまっていました…。

• Button1_MouseLeave と共に TableLayoutPanel1_MouseEnter が発生することになります。
• Button1_MouseEnter と共に TableLayoutPanel1_MouseLeave が発生することになります。
投稿者 レモネード  (その他) 投稿日時 2022/9/9 18:51:20
丁寧にありがとうございます。
一度試していたので、問題点があることと、魔界の仮面弁士様の言ってることも伝わっておりました。
また私の認識が間違っていなければ、おそらく両方MouseEnterと読んでしまっても、読める気はしました。
投稿者 レモネード  (その他) 投稿日時 2022/9/9 18:53:13
すみません解決済みマークが外れてしまったようなので付け直します。