18

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.

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
Ananth
  • 10,330
  • 24
  • 82
  • 109

2 Answers2

49

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • MessageBox uses the Win32 API method GetActiveWindow which can return a window that doesn't belong to your app. It's better not to rely on this and always specify the owner window yourself. – Tergiver Sep 20 '10 at 13:24
  • 1
    @Tergiver: GetActiveWindow can only return a window handle for a window that was created on the same thread. http://msdn.microsoft.com/en-us/library/ms646292%28VS.85%29.aspx – Hans Passant Sep 20 '10 at 13:28
  • You are correct. I apologize, I seem to be getting my API methods confused. – Tergiver Sep 20 '10 at 13:35
  • I came across this problem in a VB6 app that I had migrated to VB.NET. In my case it wasn't assigning to any of the properties specified, but it was causing the window handle to be created earlier in the form's lifecycle because of an API call that was carried across from the VB6 code. Delaying the API call until after the window handle has been created in the normal course of events avoided the problem. – JDHnz Jan 20 '11 at 06:28
  • @HansPassant can you give me some advice please. This problem has manifested itself because I have to allow the user to change the layout of MDI forms of an application - when this happens, the above manifests itself. I can catch the fact that the collection is zero and just show the massage box without a parent - but is there any other way to get the active form handle? – MoonKnight Jan 31 '14 at 10:37
  • Every problem is unique. I listed the properties that are dangerous and none have anything to do with MDI layout. You'll need to click the Ask Question button to show what you are doing. – Hans Passant Jan 31 '14 at 11:07
  • I can't believe they never fixed this bug... This means I cannot use `Application.OpenForms` to invoke callbacks on the UI thread. I dislike the pain of having to lug around a reference to a control everytime I want to change threads. – Eldritch Conundrum Mar 27 '15 at 10:27
  • You shouldn't be doing that anyway, I expanded the answer a bit to show the correct way. – Hans Passant Mar 27 '15 at 11:09
1

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);
    }
}
Roel
  • 3,089
  • 2
  • 30
  • 34
Wolf5
  • 16,600
  • 12
  • 59
  • 58
  • It does make the Form show up and hide in the taskbar but it also makes my forms window invisible and I'm unable to get it visible again. – Roel Nov 20 '18 at 10:18