Reputation: 121
I have a simple form with a button Button1
that, once pressed, it opens a new _frm
as follows:
Protected _frm As Form
[...]
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
_frm = New Form()
AddHandler _frm.Deactivate, AddressOf OnFormDeactivated
AddHandler _frm.FormClosed, AddressOf OnFormClosed
With _frm
.Location = PointToScreen(New Point(0, Height))
.Text = "_frm"
.Show(Me.FindForm)
End With
End Sub
And the handlers for the Deactivate
and FormClosed
events are defined as follows:
Private Sub OnFormDeactivated(sender As Object, e As EventArgs)
_frm.Close()
End Sub
Private Sub OnFormClosed(sender As Object, e As FormClosedEventArgs)
Me.FindForm.RemoveOwnedForm(_frm)
RemoveHandler _frm.Deactivate, AddressOf OnFormDeactivated
RemoveHandler _frm.FormClosed, AddressOf OnFormClosed
_frm.Dispose()
_frm = Nothing
WindowUtil.ShowWindow(Process.GetCurrentProcess) 'call to user32.dll ShowWindow
WindowUtil.ToForeground(Process.GetCurrentProcess) 'call to user32.dll SetForegroundWindow
End Sub
When I close the child Form, the Parent form gets the focus back and the same is true if I select another application, which is the desired behaviour.
But if I click anywhere inside the Parent Form, the Child Form disappears correctly but the Parent Form (and therefore the entire application) loses focus.
EDIT
WindowUtil.vb
methods:
<DllImport("user32.dll", EntryPoint:="SetForegroundWindow",
CallingConvention:=CallingConvention.StdCall,
CharSet:=CharSet.Unicode, SetLastError:=True)>
Private Shared Function SetForegroundWindow(ByVal handle As IntPtr) As Boolean
End Function
Public Shared Sub ToForeground(proc As Process)
Dim ForegroundReturn As Boolean = SetForegroundWindow(proc.MainWindowHandle)
End Sub
<DllImport("user32.dll", EntryPoint:="ShowWindow",
CallingConvention:=CallingConvention.StdCall,
CharSet:=CharSet.Unicode, SetLastError:=True)>
Private Shared Function ShowWindow(ByVal handle As IntPtr, ByVal nCmd As Int32) As Boolean
End Function
Public Shared Sub ShowWindow(proc As Process)
Dim ShowReturn As Boolean = ShowWindow(proc.MainWindowHandle, 1)
End Sub
Upvotes: 1
Views: 779
Reputation: 32248
I suggest to add a delay between the BringToFront()
/ Activate()
methods calls and the Form.Close()
call that closes the owned Form: it should give more stable results in both situations: when you click the Owner Form and when you click a Window that belongs to another application.
As a note:
That's why PInvoking is not necessary (meaning, the Framework already does this for you).
Form.Close()
is called after the the activation of the Owner Form because this causes the Control (in the Owner Form) that receives the mouse action to become the ActiveControl
, without setting this property explicitly.
► The 15
milliseconds delay can be fine-tuned, since the nature of the Form (how it's built and how many Controls it contains) can affect the time it needs to close (because OwnerForm.Activate()
is called right before returning from the async ActivateForm()
method and the Form on top is closed right after).
This generates a smoother transition, but it may also cause the Owner Form to blink when you click the Window that belongs to another Process.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' someForm is clearly a made-up class name :) Set it to actual the class name.
Dim frm As New someForm()
AddHandler frm.Deactivate, AddressOf OnFormDeactivated
With frm
' Whatever this means to you
.Location = PointToScreen(New Point(0, Height))
.Text = "frm"
.Show(Me)
End With
End Sub
Private Async Sub OnFormDeactivated(sender As Object, e As EventArgs)
Dim frm = DirectCast(sender, Form)
RemoveHandler frm.Deactivate, AddressOf OnFormDeactivated
Await ActivateForm(Me)
frm.Close()
End Sub
Private Async Function ActivateForm(frm As Form) As Task
Await Task.Delay(15)
frm.BringToFront()
Await Task.Delay(15)
frm.Activate()
End Function
Upvotes: 1