1

I need to create a custom MessageBox in C# Framework 3.5 which displays some MesageBoxButtons, and returns the DialogResult value. If there is no user reaction, after a certain timeout time, the MessageBox should close, returning null.

I followed DmitryG's answer here, with minor changes:

static DialogResult? dialogResult_ = null;

public AutoClosingMessageBox(string text, string caption, int timeout, MessageBoxButtons msbb)
{
  _caption = caption;
  _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
      null, timeout, System.Threading.Timeout.Infinite);

  dialogResult_ = MessageBox.Show(text, caption, msbb);
}

public static DialogResult? Show(string text, string caption, int timeout, MessageBoxButtons efb)
{
  new AutoClosingMessageBox(text, caption, timeout, efb);
  return dialogResult_;
}

void OnTimerElapsed(object state)
{
  IntPtr mbWnd = FindWindow("#32770", _caption);
  if (mbWnd != IntPtr.Zero)
  {
    SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
    _timeoutTimer.Dispose();
  }

  dialogResult_ = null;
}

    const int WM_CLOSE = 0x0010;
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

To create the MessageBox we only have to call the Show function

AutoClosingMessageBox.Show("Show me sth", "capt", 3000, MessageBoxButtons.AbortRetryIgnore);

This approach does return the dialogResult_ value when the user clicks on a button within the MessageBox, but the WM_Close message doesn't close the MessageBox anymore after the timeout time.

Is this because the MessageBox is still waiting for the Dialog Result? If yes, how can I avoid it? I would like to avoid having to start the Message Box in a new thread and having to kill the thread.

Community
  • 1
  • 1
AlexGuevara
  • 932
  • 11
  • 28
  • Possibly you should use `PostMessage` instead of `SendMessage` when the timer completes. – Dbuggy Dec 08 '15 at 10:03
  • The main difference I am aware of is that SendMessage waits for the message to be processed, which is the case. Using PostMessage doesn't help unfortunately. – AlexGuevara Dec 08 '15 at 10:23
  • From what i noticed. When supplying the `MessageBoxButtons` argument to `MessageBox.Show` the X icon on the top right of the message box window is grayed. This does not happen when you do not supply the MessageBoxButtons argument. This lead me to believe that the internals of the Message box is ignoring or not recieving the WM_CLOSE event. – Dbuggy Dec 08 '15 at 11:00
  • 1
    The X (close) icon on the window corresponds with the Cancel button. If you would specify `MessageBoxButtons.RetryCancel` for example the X button is active and the window gets closed correctly. – Dbuggy Dec 08 '15 at 11:08
  • Well recognized! It seems that it works only if the Cancel button is present, in which case the X (close) is marked, and then returns Cancel if nothing is clicked on. It also works if only the Ok button is present, returning Ok. What doesn't work is the Yes/No combination and Abort/Retry/Ignore. Interesting! Any ideas for a workaround? – AlexGuevara Dec 08 '15 at 12:28
  • 1
    1- Implement your own CustomMessageBox using a form (don't use the MessageBox class) 2- Try hacking into the MessageBox window so that cancel will be supported... won't advise this. – Dbuggy Dec 08 '15 at 13:39
  • Agreed, It's so easy to make your own custom form for this and just add a form timer that will simply set the dialogresult value and close. – Moon Dec 08 '15 at 23:20

1 Answers1

1

I agree with the other comments, that you should just make your own message box form.

That said, if you still want to use that other approach, you should be able to get it to work by sending an appropriate message to the dialog that is recognized; e.g. Alt-I for "ignore".

Here's a version of the code you posted that does that:

class AutoClosingMessageBox
{
    System.Threading.Timer _timeoutTimer;
    string _caption;
    static DialogResult? dialogResult_ = null;

    private AutoClosingMessageBox(string text, string caption, int timeout, MessageBoxButtons msbb)
    {
        _caption = caption;
        _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
            null, timeout, System.Threading.Timeout.Infinite);

        dialogResult_ = MessageBox.Show(text, caption, msbb);
    }

    public static DialogResult? Show(string text, string caption, int timeout, MessageBoxButtons efb)
    {
        new AutoClosingMessageBox(text, caption, timeout, efb);
        return dialogResult_;
    }

    void OnTimerElapsed(object state)
    {
        IntPtr mbWnd = FindWindow("#32770", _caption);
        if (mbWnd != IntPtr.Zero)
        {
            SetForegroundWindow(mbWnd);
            SendKeys.SendWait("%I");
            _timeoutTimer.Dispose();
        }

        dialogResult_ = null;
    }

    [DllImport("user32.dll", SetLastError = true)]
    extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
    [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    extern static IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll", SetLastError = true)]
    extern static bool SetForegroundWindow(IntPtr hwnd);

}

The SendKeys class only works on the current active window, so I included a call to SetForegroundWindow() to ensure the keys get to the correct window.

Of course, the above hard-codes the Alt-I required. If you wanted a more general solution, you might include a dictionary that maps the MessageBoxButtons value to the appropriate SendKeys string needed to dismiss that dialog and/or have the caller provide that information (either force them to provide the actual SendKeys string or (nicer) have them pass an enum value indicating which button they want to use to dismiss the dialog and then have your implementation map that to the appropriate string.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136