如何检测嵌入Panel的MDI子窗体鼠标点击以让父窗体获焦?
问题:嵌入Panel的外部程序点击后无法让主窗体获得焦点
我使用以下代码启动记事本并将其嵌入到窗体的Panel1中:
Public Class Form1 Private Const WM_SYSCOMMAND As Integer = 274 Private Const SC_MAXIMIZE As Integer = 61488 Declare Auto Function SetParent Lib "user32.dll" (ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As Integer Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim proc As Process proc = Process.Start("notepad.exe") proc.WaitForInputIdle() SetParent(proc.MainWindowHandle, Me.Panel1.Handle) SendMessage(proc.MainWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0) End Sub End Class
当前遇到的问题:当其他外部程序位于我的窗体前方时,点击窗体标题栏可以将主窗体带到前台,但点击嵌入在Panel里的记事本区域时,主窗体不会获得焦点,也无法被前置。请问有没有办法检测到这个嵌入的外部程序上的鼠标点击,从而让主窗体获得焦点?
解决方案
方法1:全局鼠标钩子监控点击
通过注册全局低级别鼠标钩子,检测点击目标是否属于嵌入的记事本进程,若符合则主动唤起主窗体。
步骤1:声明必要API与变量
Imports System.Runtime.InteropServices Public Class Form1 Private Const WM_SYSCOMMAND As Integer = 274 Private Const SC_MAXIMIZE As Integer = 61488 Private Const WH_MOUSE_LL As Integer = 14 Private Const WM_LBUTTONDOWN As Integer = &H201 Declare Auto Function SetParent Lib "user32.dll" (ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As Integer Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer Declare Auto Function SetForegroundWindow Lib "user32.dll" (ByVal hWnd As IntPtr) As Boolean Declare Auto Function SetWindowsHookEx Lib "user32.dll" Alias "SetWindowsHookExA" (ByVal idHook As Integer, ByVal lpfn As HookProc, ByVal hMod As IntPtr, ByVal dwThreadId As Integer) As IntPtr Declare Auto Function CallNextHookEx Lib "user32.dll" (ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr Declare Auto Function UnhookWindowsHookEx Lib "user32.dll" (ByVal hhk As IntPtr) As Boolean Declare Auto Function WindowFromPoint Lib "user32.dll" (ByVal pt As POINT) As IntPtr Declare Auto Function GetParent Lib "user32.dll" (ByVal hWnd As IntPtr) As IntPtr Private Delegate Function HookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr Private mouseHook As IntPtr Private hookProc As HookProc Private notepadHandle As IntPtr ' 辅助结构体 Private Structure POINT Public x As Integer Public y As Integer End Structure Private Structure MSLLHOOKSTRUCT Public pt As POINT Public mouseData As Integer Public flags As Integer Public time As Integer Public dwExtraInfo As IntPtr End Structure
步骤2:初始化钩子与嵌入程序
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim proc As Process = Process.Start("notepad.exe") proc.WaitForInputIdle() notepadHandle = proc.MainWindowHandle SetParent(notepadHandle, Me.Panel1.Handle) SendMessage(notepadHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0) ' 注册全局鼠标钩子 hookProc = New HookProc(AddressOf MouseHookProc) mouseHook = SetWindowsHookEx(WH_MOUSE_LL, hookProc, Marshal.GetHINSTANCE(GetType(Form1).Module), 0) End Sub
步骤3:钩子处理逻辑
Private Function MouseHookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr If nCode >= 0 AndAlso wParam = WM_LBUTTONDOWN Then Dim mouseInfo As MSLLHOOKSTRUCT = Marshal.PtrToStructure(lParam, GetType(MSLLHOOKSTRUCT)) Dim clickedHandle As IntPtr = WindowFromPoint(mouseInfo.pt) ' 向上遍历父窗口,确认是否属于记事本进程 Dim parentHandle As IntPtr = clickedHandle Do While parentHandle <> IntPtr.Zero AndAlso parentHandle <> notepadHandle parentHandle = GetParent(parentHandle) Loop If parentHandle = notepadHandle Then ' 将主窗体前置 SetForegroundWindow(Me.Handle) End If End If Return CallNextHookEx(mouseHook, nCode, wParam, lParam) End Function
步骤4:窗体关闭时卸载钩子
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed If mouseHook <> IntPtr.Zero Then UnhookWindowsHookEx(mouseHook) End If End Sub End Class
方法2:子类化Panel拦截消息
通过子类化Panel,拦截WM_PARENTNOTIFY消息,当嵌入的子窗口触发鼠标点击时,主动激活主窗体。这种方式无需全局钩子,更轻量。
步骤1:创建子类化Panel
Public Class SubclassedPanel Inherits Panel Private Const WM_PARENTNOTIFY As Integer = &H210 Private Const WM_LBUTTONDOWN As Integer = &H201 Protected Overrides Sub WndProc(ByRef m As Message) ' 检测子窗口的左键点击通知 If m.Msg = WM_PARENTNOTIFY AndAlso m.WParam.ToInt32() = WM_LBUTTONDOWN Then Me.FindForm().Activate() End If MyBase.WndProc(m) End Sub End Class
步骤2:替换原有Panel
在窗体设计器中,将原有的Panel1替换为SubclassedPanel(可通过右键控件→更改类型实现),然后保留原有的嵌入记事本代码即可。
内容的提问来源于stack exchange,提问作者Eric van Loon
相关产品推荐
相关产品推荐

