Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Application.OpenForms.Count = 0 always

Tags:

c#

.net

winforms

I have this situation. Application.OpenForms doesnt return the right result. ie Application.OpenForms.Count = 0 always..

Purpose of getting the form is get the owner of the Form so that I can pass the owner as the parameter of the MessageBox.Show() function.

like image 646
Ananth Avatar asked Sep 20 '10 12:09

Ananth


2 Answers

There's a bug in Windows Forms that makes a form disappear from the Application.OpenForms collection. This will happen when you assign the ShowInTaskbar, FormBorderStyle, ControlBox, Min/MaximizedBox, RightToLeftLayout, HelpButton, Opacity, TransparencyKey, ShowIcon or MdiParent property after the window was created. These properties are special in that they are specified as style flags in the native CreateWindowEx() call. This sample form demonstrates the bug:

public partial class Form1 : Form {
    public Form1() {
        InitializeComponent();
        button1.Click += button1_Click;
    }
    private void button1_Click(object sender, EventArgs e) {
        Console.WriteLine(Application.OpenForms.Count);
        this.ShowInTaskbar = !this.ShowInTaskbar;
        Console.WriteLine(Application.OpenForms.Count);
    }
}

Windows Forms must call CreateWindowEx() again to make the changed property effective, passing different style flags. Destroying the original window first has side effects beyond the very noticeable flicker, one of them is that the Application class loses track of the form since it sees the window disappear. With the bug that it doesn't add it back when the new window is created. Avoid the bug by setting the property only in the constructor, code that runs before CreateWindowEx() is called, not in any event handlers.

In general, avoid relying on OpenForms due to this bug. Give the class that needs to display the message box a reference to the form instance through its constructor. MessageBox usually figures out a parent window by itself correctly btw, it will pick the active window and that's correct 99% of the time. If you need it to call BeginInvoke() from a worker thread then be sure to copy SynchronizationContext.Current in your constructor and call its Post() method later. Ensures your library will also work with other GUI class libraries.

like image 97
Hans Passant Avatar answered Nov 19 '22 01:11

Hans Passant


I got the issue when I used ShowInTaskBar = true. I solved it by using windows API instead of the .Net properties. Application.OpenForms remained intact.

I do not know if it works as a general workaround using SetWindowLong to change the properties but it works for ShowInTaskBar = true.

 public static class ShowInTaskBar {

    [DllImport("User32.dll")]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    [DllImport("User32.dll")]
    private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    private const int SW_HIDE = 0x00;
    private const int SW_SHOW = 0x05;

    private const int WS_EX_APPWINDOW = 0x40000;
    private const int GWL_EXSTYLE = -0x14;

    public static void ShowWindowInTaskbar(IntPtr pMainWindow) {
        SetWindowLong(pMainWindow, GWL_EXSTYLE, GetWindowLong(pMainWindow, GWL_EXSTYLE) | WS_EX_APPWINDOW);

        ShowWindow(pMainWindow, SW_HIDE);
        ShowWindow(pMainWindow, SW_SHOW);
    }

    public static void HideWindowFromTaskbar(IntPtr pMainWindow) {
        SetWindowLong(pMainWindow, GWL_EXSTYLE, GetWindowLong(pMainWindow, GWL_EXSTYLE) & ~WS_EX_APPWINDOW);

        ShowWindow(pMainWindow, SW_HIDE);
        ShowWindow(pMainWindow, SW_SHOW);
    }
}
like image 31
Wolf5 Avatar answered Nov 19 '22 01:11

Wolf5