28

How, being inside the main form of my WinForm app can I tell if there are any modal windows/dialogs open that belong to the main form?

user18046
  • 301
  • 1
  • 3
  • 4

7 Answers7

36
if (this.Visible && !this.CanFocus)
{
    // modal child windows are open
}
Bitterblue
  • 13,162
  • 17
  • 86
  • 124
  • This works for me, but note that 'this' must be the form and not a control within the form. I made this mistake initially and possible the downvoter did too. – Nick Betts Apr 26 '22 at 05:49
6

You maybe can use the events for EnterThreadModal and LeaveThreadModal. Here is an example how you can do it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Application.EnterThreadModal += new EventHandler(Application_EnterThreadModal);
            Application.LeaveThreadModal += new EventHandler(Application_LeaveThreadModal);

            Application.Run(new Form1());
        }

        private static void Application_EnterThreadModal(object sender, EventArgs e)
        {
            IsModalDialogOpen = true;
        }

        private static void Application_LeaveThreadModal(object sender, EventArgs e)
        {
            IsModalDialogOpen = false;
        }

        public static bool IsModalDialogOpen { get; private set; }
    }
}
jreichert
  • 1,466
  • 17
  • 31
6

Long story short: opening a modal form is blocks execution on the main form as long as the modal window is open, so your main form can never check to see if its opened any modal forms until after the modal form has closed. In other words, your question is based on a misunderstanding of how modal forms work, so its moot altogether.

For what its worth, it is possible to tell if there are any modal forms open:

foreach (Form f in Application.OpenForms)
{
    if (f.Modal)
    {
        // do stuff
    }
}
Juliet
  • 80,494
  • 45
  • 196
  • 228
  • 14
    This is not actually true. If it were true then whenever you moved a File Open dialog accross the owning form it would fail to repaint. So at the least painting will run. Once painting is running you can do anything. There are several windows messages which get through and allow for code execution – JaredPar Jan 04 '09 at 00:17
  • 3
    Agree with JaredPar, this is a sensible question. E.g., what if you were checking on a different thread, prior to Invoking to show a new dialog? Still, the code sample looks good. – John Oct 06 '09 at 11:01
  • 3
    Unfortunately this code doesn't detect modal messages boxes (e.g. when MessageBox.Show is used) – Martin Sherburn Aug 24 '11 at 21:25
  • 4
    JaredPar is right, it is definitely not true that the message pump is kaput until the modal form is closed. I have a program that uses mainForm.BeginInvoke() to dispatch a method on the GUI thread from a worker thread. This works fine even when a modal form is being displayed. Somewhat to my surprise this meant that my program could display multiple copies of a modal form. (This wasn't my intention, now I need to detect and avoid this situation.) – RenniePet Feb 26 '12 at 23:37
1

This is an illustration why this answer is correct and the assumptions made in this answer are sometimes wrong.

if (MyForm.CanFocus == false && MyForm.Visible == true)
{
    // we are modal            
}

This works for modal shown sub Forms and for MessageBoxes.


Some demo code:

private void modalTest_Click(object sender, EventArgs e)
{
    // Timer which fires 100ms after the first message box is shown
    System.Windows.Forms.Timer canFocusTimer = new System.Windows.Forms.Timer();
    canFocusTimer.Tick += CanFocusTimer_Tick;
    canFocusTimer.Interval = 100;
    canFocusTimer.Start();
    
    // First MessageBox shows that CanFocus == true
    MessageBox.Show($"First MessageBox: { nameof(this.CanFocus) } == { this.CanFocus }");
}

private void CanFocusTimer_Tick(object sender, EventArgs e)
{
    (sender as System.Windows.Forms.Timer).Stop();

    // Even though there is already a MessageBox shown the second gets
    // displayed. But now CanFocus == false
    MessageBox.Show($"Second MessageBox: { nameof(this.CanFocus) } == { this.CanFocus }");
}

Result:

enter image description here

marsh-wiggle
  • 2,508
  • 3
  • 35
  • 52
0

While using CanFocus works the majority of the time, I was hesitant to use it because technically, a window can be unfocusable for another reason, even if it doesn't own any dialog. One example is if the Enabled property of the form is set to false, but there may potentially be other reasons as well. In my case, I wanted to be sure that there is actually a dialog preventing interaction with the window, so there wouldn't be a false positive if the window couldn't be focused for another reason.

To accomplish this, I imported the GetWindow function of USER32.DLL, as follows.

public enum GW : uint {
    GW_CHILD = 5,
    GW_ENABLEDPOPUP = 6,
    GW_HWNDFIRST = 0,
    GW_HWNDLAST = 1,
    GW_HWNDNEXT = 2,
    GW_HWNDPREV = 3,
    GW_OWNER = 4
}

[DllImport("USER32.DLL", SetLastError = true)]
public static extern IntPtr GetWindow(IntPtr hWnd, GW uCmd);

Then, I used it to write the following code. Note that Handle is a form property that gets the form's window handle.

if (!CanFocus) {
    // if there is a window above us in the z-order
    IntPtr previousWindow = GetWindow(Handle, GW.GW_HWNDPREV);

    if (previousWindow != IntPtr.Zero) {
        // if we own the window above us in the z-order
        if (Handle == GetWindow(previousWindow, GW.GW_OWNER)) {
            // a dialog is preventing focus to our window
            return true;
        }
    }
}
// there are no dialogs preventing focus to our window
return false;

This code works according to the principles described in the article Window Features, specifically:

An owned window is always above its owner in the z-order.

And, according to the documentation for the GetWindow function, when the GW_HWNDPREV flag is specified...

The retrieved handle identifies the window above the specified window in the Z order.

Emphasis mine. So according to this, an owned window is always above its owner in the z-order, and calling GetWindow with GW_HWNDPREV gets the window above our window in the z-order. Therefore, if our window owns any windows, calling GetWindow with our window's handle and GW_HWNDPREV will get us an owned window (there can be multiple, but we only need to confirm there is at least one.)

However, if the window above us in the z-order is not an owned window, then we know that our window doesn't own any windows. So we get the potentially owned window with GW_HWNDPREV, then confirm our window owns it with GW_OWNER.

It is significant whether or not our window owns another window, because if it doesn't own another window, no other window will get in the way of our window when the user attempts to switch to it, even if it is indeed disabled.

If this test passes, our window owns another window. If our window can't be focused as tested with CanFocus, and our window owns another window, then the owned window is a dialog that prevents focus to our window.

*To be specifically clear, the definition "an owned window is always above its owner in the z-order" is slightly ambiguous. For example, it doesn't make it clear whether it is possible to have an owned window, above a standard window, above an owner window. The owned window would still technically be above the owner in the z-order in this configuration. In practice however, it is clear that the intended interpretation is that an owner window is always together with its owned windows, with the owned windows as a group being immediately above the owner. I was unable to get a standard window to be between an owned window and its owner window when testing.

tomysshadow
  • 870
  • 1
  • 8
  • 23
-1

Timers still run and fire events.
Example that works...

public partial class Form1 : Form
{
    Form2 f2 = new Form2();
    public Form1()
    {
        InitializeComponent();
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        f2.UpdateData(DateTime.Now.ToString());
        if (!f2.Visible) f2.ShowDialog();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        f2.ShowDialog();
        MessageBox.Show("Done");
    }
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
SnowMan50
  • 7
  • 2
-1

If you Google a bit you will find that Form.ShowDialog() disables other forms to prevent user input to those forms of the current one. But most everything else (like timers and other events from sources external to the displayed form) continues to run.

SnowMan50
  • 7
  • 2