vb.netwinformswinapiparent-childmdi

How to move a Form to the foreground when clicking a Window of another app parented to a Panel


I use the code down below to start an application and move it into a Panel on my Form. In this example I use Notepad, only as an example. Later I will use a different application.

When another application is moved in front of my Form, I can only move my Form to the foreground by clicking the title bar. If I click on the MDI child area (so the Panel where Notepad is moved into), nothing happens.
Is there a way to enable that?

Imports System.Runtime.InteropServices
Public Class Form1
    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, 274, 61488, 0)
    End Sub
End Class

Solution

  • The problem is that a hosted (re-parented) Window, when activated, doesn't cause the hosting Form to also activate, so it's brought to the foreground.
    The hosted Window is not exactly a child Window and the hosting Form doesn't receive any message from it.

    A possible method to bring to the foreground the Form that hosts a foreign Window, when this Window receives the focus (you click or otherwise activate it).
    It uses SetWinEventHook to install a Hook that monitors changes in state of the placement of a Window (EVENT_SYSTEM_FOREGROUND).
    You specify the handle of the Window of interest, (your proc.MainWindowHandle here), plus its ProcessId and ThreadId. Note that these are not the same as your app's, a call to GetWindowThreadProcessId() is required to get this information.

    When you activate the foreign Window, the Hook calls the specified callback delegate (here, ForegroundChangedEventDelegate), which in turn executes the method that it points to (ForegroundStateChangedCallback)
    When this method is called, it checks whether the object that caused the notification is OBJID_WINDOW and that the event is actually EVENT_SYSTEM_FOREGROUND. If that's the case, it calls SetWindowPos to bring the hosting Form to the foreground, but without activating it, so the hosted Window doesn't lose focus

    Notes:

    this means that you have to specify the namespace of your app for the import to work as intended. In VB.NET, usually the name of the app and its main Namespace match; if your app is named, WinFormsApp1, then it's Imports WinFormsApp1.NativeMethods


    I suggest using the code in Unhook Window into its original State to set the Parent (and, possibly, set it back to the original value before closing the hosting form). You can send WM_CLOSE to the Window if needed.


    Imports [Your App Namespace].NativeMethods
    
    Public Class SomeForm
    
        Private hForegrundChangedEventHook As IntPtr
        Private ReadOnly ForegrundChangedEventDelegate As WinEventDelegate
        Private Shared GCForegroundStateSafetyHandle As GCHandle
    
        Public Sub New()
            InitializeComponent()
            ForegrundChangedEventDelegate = New WinEventDelegate(AddressOf ForegroundStateChangedCallback)
            GCForegroundStateSafetyHandle = GCHandle.Alloc(ForegrundChangedEventDelegate)
        End Sub
    
        Protected Overrides Sub OnHandleDestroyed(e As EventArgs)
            GCForegroundStateSafetyHandle.Free()
            UnhookWinEvent(hForegrundChangedEventHook)
            MyBase.OnHandleDestroyed(e)
        End Sub
    
        Private Sub SetForegroundStateChangedHook(hWnd As IntPtr)
            Dim processId As UInteger
            Dim targetThreadId = GetWindowThread(hWnd, processId)
            hForegrundChangedEventHook = WinEventHookOne(SWEH_Events.EVENT_SYSTEM_FOREGROUND, ForegrundChangedEventDelegate, processId, targetThreadId)
        End Sub
    
        Friend Sub ForegroundStateChangedCallback(hWinEventHook As IntPtr, eventType As SWEH_Events, hWnd As IntPtr, idObject As SWEH_ObjectId, idChild As Long, dwEventThread As UInteger, dwmsEventTime As UInteger)
            If idObject = SWEH_ObjectId.OBJID_WINDOW AndAlso eventType = SWEH_Events.EVENT_SYSTEM_FOREGROUND Then
                Dim flags = SWP_Flags.SWP_ASYNCWINDOWPOS Or SWP_Flags.SWP_NOACTIVATE Or SWP_Flags.SWP_NOSIZE Or SWP_Flags.SWP_NOMOVE
                SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0, flags)
            End If
        End Sub
    End Class
    

    NativeMethods class

    Add this class to the Project and import in your Form as described

    Imports System.Runtime.InteropServices
    
    Public Class NativeMethods
    
        <DllImport("user32.dll", SetLastError:=True)>
        Friend Shared Function GetWindowThreadProcessId(hWnd As IntPtr, ByRef lpdwProcessId As UInteger) As UInteger
        End Function
    
        <DllImport("user32.dll", SetLastError:=True)>
        Friend Shared Function SetWindowPos(hWnd As IntPtr, hWndInsertAfter As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, uFlags As SWP_Flags) As Boolean
        End Function
    
        Friend Delegate Sub WinEventDelegate(
            hWinEventHook As IntPtr,
            eventType As SWEH_Events,
            hwnd As IntPtr, idObject As SWEH_ObjectId,
            idChild As Long,
            dwEventThread As UInteger,
            dwmsEventTime As UInteger)
    
        <DllImport("user32.dll", SetLastError:=False)>
        Friend Shared Function SetWinEventHook(
            eventMin As SWEH_Events,
            eventMax As SWEH_Events,
            hmodWinEventProc As IntPtr,
            lpfnWinEventProc As WinEventDelegate,
            idProcess As UInteger,
            idThread As UInteger,
            dwFlags As SWEH_dwFlags) As IntPtr
        End Function
    
        <DllImport("user32.dll", SetLastError:=False)>
        Friend Shared Function UnhookWinEvent(hWinEventHook As IntPtr) As Boolean
        End Function
    
        Friend Shared WinEventHookInternalFlags As SWEH_dwFlags =
            SWEH_dwFlags.WINEVENT_OUTOFCONTEXT Or SWEH_dwFlags.WINEVENT_SKIPOWNPROCESS
    
    
        Friend Shared Function WinEventHookOne(evt As SWEH_Events, weDelegate As WinEventDelegate, idProcess As UInteger, idThread As UInteger) As IntPtr
            Return SetWinEventHook(evt, evt, IntPtr.Zero, weDelegate, idProcess, idThread, WinEventHookInternalFlags)
        End Function
    
    
        Friend Shared Function GetWindowThread(hWnd As IntPtr, ByRef processID As UInteger) As UInteger
            processID = 0
            Return GetWindowThreadProcessId(hWnd, processID)
        End Function
    
    
        ' SetWinEventHook Events
        Friend Enum SWEH_Events As UInteger
            EVENT_MIN = &H1
            EVENT_MAX = &H7FFFFFFF
            EVENT_SYSTEM_SOUND = &H1
            EVENT_SYSTEM_ALERT = &H2
            EVENT_SYSTEM_FOREGROUND = &H3
            EVENT_SYSTEM_MENUSTART = &H4
            EVENT_SYSTEM_MENUEND = &H5
            EVENT_SYSTEM_MENUPOPUPSTART = &H6
            EVENT_SYSTEM_MENUPOPUPEND = &H7
            EVENT_SYSTEM_CAPTURESTART = &H8
            EVENT_SYSTEM_CAPTUREEND = &H9
            EVENT_SYSTEM_MOVESIZESTART = &HA
            EVENT_SYSTEM_MOVESIZEEND = &HB
            EVENT_SYSTEM_CONTEXTHELPSTART = &HC
            EVENT_SYSTEM_CONTEXTHELPEND = &HD
            EVENT_SYSTEM_DRAGDROPSTART = &HE
            EVENT_SYSTEM_DRAGDROPEND = &HF
            EVENT_SYSTEM_DIALOGSTART = &H10
            EVENT_SYSTEM_DIALOGEND = &H11
            EVENT_SYSTEM_SCROLLINGSTART = &H12
            EVENT_SYSTEM_SCROLLINGEND = &H13
            EVENT_SYSTEM_SWITCHSTART = &H14
            EVENT_SYSTEM_SWITCHEND = &H15
            EVENT_SYSTEM_MINIMIZESTART = &H16
            EVENT_SYSTEM_MINIMIZEEND = &H17
            EVENT_SYSTEM_DESKTOPSWITCH = &H20
            EVENT_SYSTEM_END = &HFF
            EVENT_OEM_DEFINED_START = &H101
            EVENT_OEM_DEFINED_END = &H1FF
            EVENT_UIA_EVENTID_START = &H4E00
            EVENT_UIA_EVENTID_END = &H4EFF
            EVENT_UIA_PROPID_START = &H7500
            EVENT_UIA_PROPID_END = &H75FF
            EVENT_CONSOLE_CARET = &H4001
            EVENT_CONSOLE_UPDATE_REGION = &H4002
            EVENT_CONSOLE_UPDATE_SIMPLE = &H4003
            EVENT_CONSOLE_UPDATE_SCROLL = &H4004
            EVENT_CONSOLE_LAYOUT = &H4005
            EVENT_CONSOLE_START_APPLICATION = &H4006
            EVENT_CONSOLE_END_APPLICATION = &H4007
            EVENT_CONSOLE_END = &H40FF
            EVENT_OBJECT_CREATE = &H8000
            EVENT_OBJECT_DESTROY = &H8001
            EVENT_OBJECT_SHOW = &H8002
            EVENT_OBJECT_HIDE = &H8003
            EVENT_OBJECT_REORDER = &H8004
            EVENT_OBJECT_FOCUS = &H8005
            EVENT_OBJECT_SELECTION = &H8006
            EVENT_OBJECT_SELECTIONADD = &H8007
            EVENT_OBJECT_SELECTIONREMOVE = &H8008
            EVENT_OBJECT_SELECTIONWITHIN = &H8009
            EVENT_OBJECT_STATECHANGE = &H800A
            EVENT_OBJECT_LOCATIONCHANGE = &H800B
            EVENT_OBJECT_NAMECHANGE = &H800C
            EVENT_OBJECT_DESCRIPTIONCHANGE = &H800D
            EVENT_OBJECT_VALUECHANGE = &H800E
            EVENT_OBJECT_PARENTCHANGE = &H800F
            EVENT_OBJECT_HELPCHANGE = &H8010
            EVENT_OBJECT_DEFACTIONCHANGE = &H8011
            EVENT_OBJECT_ACCELERATORCHANGE = &H8012
            EVENT_OBJECT_INVOKED = &H8013
            EVENT_OBJECT_TEXTSELECTIONCHANGED = &H8014
            EVENT_OBJECT_CONTENTSCROLLED = &H8015
            EVENT_SYSTEM_ARRANGMENTPREVIEW = &H8016
            EVENT_OBJECT_END = &H80FF
            EVENT_AIA_START = &HA000
            EVENT_AIA_END = &HAFFF
        End Enum
    
        ' SetWinEventHook Window Objects
        Friend Enum SWEH_ObjectId As Long
            OBJID_WINDOW = &H0
            OBJID_SYSMENU = &HFFFFFFFFUI
            OBJID_TITLEBAR = &HFFFFFFFEUI
            OBJID_MENU = &HFFFFFFFDUI
            OBJID_CLIENT = &HFFFFFFFCUI
            OBJID_VSCROLL = &HFFFFFFFBUI
            OBJID_HSCROLL = &HFFFFFFFAUI
            OBJID_SIZEGRIP = &HFFFFFFF9UI
            OBJID_CARET = &HFFFFFFF8UI
            OBJID_CURSOR = &HFFFFFFF7UI
            OBJID_ALERT = &HFFFFFFF6UI
            OBJID_SOUND = &HFFFFFFF5UI
            OBJID_QUERYCLASSNAMEIDX = &HFFFFFFF4UI
            OBJID_NATIVEOM = &HFFFFFFF0UI
        End Enum
    
        ' WinEventDelegate flags
        Friend Enum SWEH_dwFlags As UInteger
            WINEVENT_OUTOFCONTEXT = &H0     ' Events are ASYNC - No dll needed
            WINEVENT_SKIPOWNTHREAD = &H1    ' Don't call back for events on installer's thread
            WINEVENT_SKIPOWNPROCESS = &H2   ' Don't call back for events on installer's process
            WINEVENT_INCONTEXT = &H4        ' Events are SYNC, this causes your dll to be injected into every process
        End Enum
    
        ' SetWindowPos flags
        <Flags>
        Public Enum SWP_Flags As UInteger
            SWP_NOSIZE = &H1
            SWP_NOMOVE = &H2
            SWP_NOZORDER = &H4
            SWP_NOREDRAW = &H8
            SWP_NOACTIVATE = &H10
            SWP_DRAWFRAME = &H20
            SWP_FRAMECHANGED = &H20
            SWP_SHOWWINDOW = &H40
            SWP_HIDEWINDOW = &H80
            SWP_NOCOPYBITS = &H100
            SWP_NOOWNERZORDER = &H200
            SWP_NOREPOSITION = &H200
            SWP_NOSENDCHANGING = &H400
            SWP_NOCLIENTSIZE = &H800
            SWP_NOCLIENTMOVE = &H1000
            SWP_DEFERERASE = &H2000
            SWP_ASYNCWINDOWPOS = &H4000
        End Enum
    End Class