Skip to content

Instantly share code, notes, and snippets.

@weltkante
Created April 30, 2019 07:05
Show Gist options
  • Save weltkante/3224f73370c94a823eb67b750484e665 to your computer and use it in GitHub Desktop.
Save weltkante/3224f73370c94a823eb67b750484e665 to your computer and use it in GitHub Desktop.
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern int MsgWaitForMultipleObjectsEx(int nCount, IntPtr pHandles, int dwMilliseconds, int dwWakeMask, int dwFlags);
private static Action _modalDoEvents;
// WinForms doesn't have a proper modal loop outside ShowDialog
// Sometimes we don't want ShowDialog, eg. for filter bar popups
// There we want the popup to close if we click outside of it, which doesn't work with ShowDialog
//
// The replacement would be message pumping with Application.DoEvents and waiting for new messages
// We can't use System.Thread.Thread.Sleep because this will not wake up when new messages arrive
// The correct solution is to use MsgWaitForMultipleObjects(Ex) which is not exposed by WinForms
//
// This method is intended to be used like this:
// while(dlg.Visible)
// DoModalLoopStep();
//
public static void DoModalLoopStep()
{
if (_modalDoEvents == null)
{
var type = typeof(Application);
var method = type.GetMethod("DoEventsModal", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
_modalDoEvents = (Action)Delegate.CreateDelegate(typeof(Action), method);
}
// This previously used Application.DoEvents() but this causes problems in Excel: it pushes a message loop with "do events" reason,
// causing Excel to intercept key input and redirect it to the main window, even though the containing message loop is modal.
// The real PropertyGrid (mentioned below) uses Application.DoEventsModal() which works in Excel but is not public.
// Since there is no other way to mimic a modal loop we'll just do the same and use reflection to invoke DoEventsModal.
_modalDoEvents();
// Flags used are:
// - 0, IntPtr.Zero = we don't wait on any handles
// - 250 ms timeout if no message arrives
// - 255 = QS_ALLINPUT & ~QS_RAWINPUT
// - 4 = MWMO_INPUTAVAILABLE
//
// The flags are derived from reflector by looking at PropertyGrid, which also shows popup controls
// (System.Windows.Forms.PropertyGridInternal.PropertyGridView.DropDownHolder.DoModalLoop)
//
MsgWaitForMultipleObjectsEx(0, IntPtr.Zero, 250, 255, 4);
}
@weltkante
Copy link
Author

WinForms has a DoModalLoop implementation which can be used like this to show a modal splash screen or popup without calling ShowDialog

popup.Show();
while (!exitModalLoop && popup.Visible)
    Utilities.DoModalLoopStep();

(where exitModalLoop is an optional additional variable to exit the loop without closing the popup)

To use it in a WPF application you may have to initialize the WinForms Application before the WPF application, at least thats what we're doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment