I have a WinForms project with a main topmost form from which a non-modal dialog is displayed. I need to hide (not close) the dialog if it loses the input focus - no matter what was the reason (the user clicked the main form, switched to another app, etc). The following bare part of the project source code shows what is going on:
public partial class MainForm : Form
{
Form _dialog = new Form();
public MainForm()
{
InitializeComponent();
this.TopMost = true;
this.Text = "Main Form";
_dialog.Text = "Dialog";
_dialog.Owner = this;
_dialog.TopMost = true;
_dialog.Deactivate += Dialog_Deactivate;
_dialog.FormClosing += Dialog_FormClosing;
}
private void Dialog_Deactivate(object sender, EventArgs e)
{
_dialog.Hide();
}
private void Dialog_FormClosing(object sender, FormClosingEventArgs e)
{
_dialog.Hide();
e.Cancel = true;
}
private void ButtonShowDialog_Click(object sender, EventArgs e)
{
_dialog.Show();
}
}
The main problem I am trying to solve is the following. If the user opened the dialog and clicks the main form like I depicted on the following screenshot
, the dialog becomes hidden as expected, but the main form loses the focus and another app that was previously active becomes active in the background - the Windows Explorer on the next screenshot:
Is it a known issue in Windows or WinForms? How to cause the main form not to lose the focus in this construction?
The issue seems to be that when the Main Form is clicked, it triggers a WM_WINDOWPOSCHANGING
event. Since the child dialog is open, the hwndInsertAfter
handle is the child dialog. But then in the Dialog_Deactivate
the child dialog is hidden, causing the Main Form to fall behind all the other windows because the hwndInsertAfter
handle is no longer a visible window.
Possible solutions are 1) set the child dialog owner to null before calling Hide()
or 2) use a different event, like LostFocus
, i.e:
public class MainForm3 : Form {
Form _dialog = null;
public MainForm3() {
this.Text = "Main Formmmmmmmm";
Button btn = new Button { Text = "Show" };
btn.Click += ButtonShowDialog_Click;
this.Controls.Add(btn);
}
bool b = false;
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (_dialog != null && _dialog.Visible)
b = true;
if (b)
Debug.WriteLine(m);
int WM_WINDOWPOSCHANGING = 0x46;
if (b && m.Msg == WM_WINDOWPOSCHANGING) {
var wp = Marshal.PtrToStructure<WINDOWPOS>(m.LParam);
Debug.WriteLine("hwnd: " + wp.hwnd + " " + GetWindowText(wp.hwnd));
Debug.WriteLine("hwndInsertAfter: " + wp.hwndInsertAfter + " " + GetWindowText(wp.hwndInsertAfter));
Debug.WriteLine("flags: " + wp.flags);
}
}
private void Dialog_Deactivate(object sender, EventArgs e) {
_dialog.Owner = null; // solution 1
_dialog.Hide();
}
private void _dialog_LostFocus(object sender, EventArgs e) { // solution 2
_dialog.Hide();
}
private void Dialog_FormClosing(object sender, FormClosingEventArgs e) {
if (_dialog.Visible) {
_dialog.Hide();
e.Cancel = true;
}
}
private void ButtonShowDialog_Click(object sender, EventArgs e) {
if (_dialog == null) {
_dialog = new Form();
_dialog.Text = "Dialoggggggg";
//_dialog.Deactivate += Dialog_Deactivate;
_dialog.LostFocus += _dialog_LostFocus; // solution 2, use LostFocus instead
_dialog.FormClosing += Dialog_FormClosing;
}
_dialog.Owner = this;
_dialog.Show();
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS {
public IntPtr hwnd, hwndInsertAfter;
public int x, y, cx, cy;
public SWP flags;
}
[Flags]
public enum SWP : uint {
SWP_ASYNCWINDOWPOS = 0x4000,
SWP_DEFERERASE = 0x2000,
SWP_DRAWFRAME = 0x0020,
SWP_FRAMECHANGED = 0x0020,
SWP_HIDEWINDOW = 0x0080,
SWP_NOACTIVATE = 0x0010,
SWP_NOCOPYBITS = 0x0100,
SWP_NOMOVE = 0x0002,
SWP_NOOWNERZORDER = 0x0200,
SWP_NOREDRAW = 0x0008,
SWP_NOREPOSITION = 0x0200,
SWP_NOSENDCHANGING = 0x0400,
SWP_NOSIZE = 0x0001,
SWP_NOZORDER = 0x0004,
SWP_SHOWWINDOW = 0x0040
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
private static String GetWindowText(IntPtr hWnd) {
StringBuilder sb = new StringBuilder(256);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
}