Force to close MessageBox programmatically
Asked Answered
I

12

26

Let me give you the background.

We have an Application(medium sized) that is using MessageBox.Show (....) at various places (in hundreds).

These message boxes are part of workflow and being used for informing,warning or taking input from an user. Application is supposed to automatically log off after certain time if there is no activity. We have a requirement that while logging out the application, just to clean the session data , to clear views and to hide itself so that in next launch, it won't have to execute the startup process which is costly in terms of time.

Everything is working fine but in a scenario when there is some message box on the screen and user left the machine without responding to message box and then due to no activity to make the application to log out. Problem is Message box won't disappear.

How I can close the opened messagebox, if any, while hiding the application?

Inexactitude answered 28/10, 2011 at 7:1 Comment(3)
Maybe send key enter or esc? :)Laurustinus
I thought MessageBox.Show(...) is modal, so how can the program send a key? Are you using threads/tasks?Pm
Thanks for replies. Just to clarify using customized msg box is not an option as rework is quite huge. Sending ESC key also not correct because only active application will receive the command. I am using FIndWindow approach where I am getting Msgbox handle by passing id and msg box caption. After getting handler I am closing using following win32 API e.g. SendMessage(new HandleRef(null, msgbxcHandler), WM_CLOSE, IntPtr.Zero, IntPtr.Zero); SendMessage(new HandleRef(null, msgbxcHandler), WM_NCDESTROY, IntPtr.Zero, IntPtr.Zero); So far its working fine.Inexactitude
P
14

Here is a piece of code based on UIAutomation (a cool but still not very used API) that attempts to close all modal windows (including the one opened with MessageBox) of the current process:

    /// <summary>
    /// Attempt to close modal windows if there are any.
    /// </summary>
    public static void CloseModalWindows()
    {
        // get the main window
        AutomationElement root = AutomationElement.FromHandle(Process.GetCurrentProcess().MainWindowHandle);
        if (root == null)
            return;

        // it should implement the Window pattern
        object pattern;
        if (!root.TryGetCurrentPattern(WindowPattern.Pattern, out pattern))
            return;

        WindowPattern window = (WindowPattern)pattern;
        if (window.Current.WindowInteractionState != WindowInteractionState.ReadyForUserInteraction)
        {
            // get sub windows
            foreach (AutomationElement element in root.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window)))
            {
                // hmmm... is it really a window?
                if (element.TryGetCurrentPattern(WindowPattern.Pattern, out pattern))
                {
                    // if it's ready, try to close it
                    WindowPattern childWindow = (WindowPattern)pattern;
                    if (childWindow.Current.WindowInteractionState == WindowInteractionState.ReadyForUserInteraction)
                    {
                        childWindow.Close();
                    }
                }
            }
        }
    }

For example, if you have a WinForms application that pops up a MessageBox when you press some button1, you will still be able to close the app using Windows "Close Window" menu (right click in the task bar):

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("Don't click me. I want to be closed automatically!");
    }

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        const int WM_SYSCOMMAND = 0x0112;
        const int SC_CLOSE = 0xF060;

        if (m.Msg == WM_SYSCOMMAND) // this is sent even if a modal MessageBox is shown
        {
            if ((int)m.WParam == SC_CLOSE)
            {
                CloseModalWindows();
                Close();
            }
        }
        base.WndProc(ref m);
    }

You could use CloseModalWindows somewhere else in your code of course, this is just a sample.

Pyramidal answered 28/10, 2011 at 8:44 Comment(6)
Isn't UIAutomation for WPF? The question was for WinFormsPiperpiperaceous
@ErikFunkenbusch - Don't know what you mean by "for WPF". UIAutomation supports any kind of application.Pyramidal
The System.Windows.Automation namespace says it's for WPF. msdn.microsoft.com/en-us/library/ms747327(v=vs.110).aspx "Microsoft UI Automation is the new accessibility framework for Microsoft Windows, available on all operating systems that support Windows Presentation Foundation (WPF)."Piperpiperaceous
Maybe, but UIAutomation is not limited to WPF, either as the client or as the server. The same article also says this: "Developers of UI Automation providers for frameworks other than WPF."Pyramidal
This meets my needs.Magdalen
I tested it in a WPF app using a timer that when ticked, called the method CloseModalWindows() and it worked great, thanks!Flypaper
P
7

This link on MSDN forums shows how to close a message box by using FindWindow and sending a WM_CLOSE message. Although the question was asked for .NET/WindowsCE, it might solve your problem, its worth a look

Polygnotus answered 28/10, 2011 at 7:22 Comment(2)
I think it's considered good practise to add at least a very little bit of additional information of how the linked page could solve a problem (instead of just posting the bare link). I hope you don't mind that I added a short description. +1 anyway, this might be a helpful resource.Stilla
Well martin, you are absolutely right. due to time complexity i wasn't able to add this info. however thanks for editing. :)Polygnotus
A
7

Refer to DmitryG post in "Close a MessageBox after several seconds"

Auto-Close MessageBox after timeout reach

using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

    public class AutoClosingMessageBox
    {
        System.Threading.Timer _timeoutTimer;
        string _caption;
        AutoClosingMessageBox(string text, string caption, int timeout)
        {
            _caption = caption;
            _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
                null, timeout, System.Threading.Timeout.Infinite);
            MessageBox.Show(text, caption);
        }
        public static void Show(string text, string caption, int timeout)
        {
            new AutoClosingMessageBox(text, caption, timeout);
        }
        void OnTimerElapsed(object state)
        {
            IntPtr mbWnd = FindWindow(null, _caption);
            if (mbWnd != IntPtr.Zero)
                SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
            _timeoutTimer.Dispose();
        }
        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);
    }

and Call it via

AutoClosingMessageBox.Show("Content", "Title", TimeOut);
Autumn answered 6/2, 2014 at 4:26 Comment(1)
it seems that this solution does not provide access to the return options of the MessageBox?Flypaper
P
3

First a Question: If messages boxes are used as part of workflow, won't programatically closing message box cause the flow to change/continue?

I think you have three options

  1. Create your own version of the messagebox class that opens a dialog window that looks like a messagebox with added functionality so it closed automatically after a period of time.

  2. Implement something like this in c# to close message boxes programtically. http://www.codeproject.com/KB/dialog/AutoCloseMessageBox.aspx

  3. Get rid of the message boxes from interupting the workflow. This is probably the best solution as from the sound of it closing a message box programatically will cause workflow to continue/change, and perhaps even cause another messagebox to show which may not be desirable. But obviously fixing the root problem might be best, but isn't always the easiest.

1 and 2 would need to be done from a separate thread, so you will need to think about the implications of that as showing the messagebox will be blocking.

Pandect answered 28/10, 2011 at 7:12 Comment(1)
+1 for point 3.! Me too, I wouldn't have a good feeling of simply "confirming" any open message box without even knowing if the user noticed it...Stilla
L
3

Heres my example with SendKeys - tested and working:

lets say we have backgroundworker and button in form. After button was click - start worker and show message box. In workers DoWork event sleep for 5s and then send enter key - messsage box closed.

private void button1_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
    MessageBox.Show("Close this message!");
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    Thread.Sleep(5000);
    SendKeys.SendWait("{Enter}");//or Esc
}
Laurustinus answered 28/10, 2011 at 7:18 Comment(4)
I just want to point out that for solution it is very important to check if there's really a message box open: an enter key sent to any other part of the User Interface might have unwanted consequences ...Stilla
Yes your point is good. Its quick workaround so enter key is obviously not good key, better to use esc - in UI it should represent cancelation.Laurustinus
This solution only works if the user doesn't change focus to another app. You can't be sure the app still has focus. And Windows prevents the app from programmatically taking focus from another app.Oina
Also, "Esc" does not work if MessageBox has Yes,No buttons.Dermatogen
M
3

This topic has been abundantly covered in other SO questions but since this particular one has several answers about using UI automation/window lookup techniques (which I don't particularly like) and generic suggestions about creating own dialog without provided code, I decided post my own solution. One can create an instantiable MessageBox like class as it follows:

using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace Common
{
    // Loosely based on: https://www.codeproject.com/Articles/17253/A-Custom-Message-Box
    class MsgBox : Form
    {
        private Panel _plHeader = new Panel();
        private Panel _plFooter = new Panel();
        private Panel _plIcon = new Panel();
        private PictureBox _picIcon = new PictureBox();
        private FlowLayoutPanel _flpButtons = new FlowLayoutPanel();
        private Label _lblMessage;

        private MsgBox()
        {
            FormBorderStyle = FormBorderStyle.FixedDialog;
            BackColor = Color.White;
            StartPosition = FormStartPosition.CenterScreen;
            MinimizeBox = false;
            MaximizeBox = false;
            ShowIcon = false;
            Width = 400;

            _lblMessage = new Label();
            _lblMessage.Font = new Font("Segoe UI", 10);
            _lblMessage.Dock = DockStyle.Fill;
            _lblMessage.TextAlign = ContentAlignment.MiddleLeft;

            _flpButtons.FlowDirection = FlowDirection.RightToLeft;
            _flpButtons.Dock = DockStyle.Fill;

            //_plHeader.FlowDirection = FlowDirection.TopDown;
            _plHeader.Dock = DockStyle.Fill;
            _plHeader.Padding = new Padding(20);
            _plHeader.Controls.Add(_lblMessage);

            _plFooter.Dock = DockStyle.Bottom;
            _plFooter.BackColor = Color.FromArgb(240, 240, 240);
            _plFooter.Padding = new Padding(10);
            _plFooter.Height = 60;
            _plFooter.Controls.Add(_flpButtons);

            _picIcon.Location = new Point(30, 50);

            _plIcon.Dock = DockStyle.Left;
            _plIcon.Padding = new Padding(20);
            _plIcon.Width = 70;
            _plIcon.Controls.Add(_picIcon);

            Controls.Add(_plHeader);
            Controls.Add(_plIcon);
            Controls.Add(_plFooter);
        }

        public static DialogResult Show(IWin32Window owner, string message, string title = null, MessageBoxButtons? buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information)
        {
            var msgBox = Create(message, title, buttons, icon);
            return msgBox.ShowDialog(owner);
        }

        public static DialogResult Show(string message, string title = null, MessageBoxButtons? buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information)
        {
            var msgBox = Create(message, title, buttons, icon);
            return msgBox.ShowDialog();
        }

        public static MsgBox Create(string message, string title = null, MessageBoxButtons? buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information)
        {
            var msgBox = new MsgBox();
            msgBox.Init(message, title, buttons, icon);
            return msgBox;
        }

        void Init(string message, string title, MessageBoxButtons? buttons, MessageBoxIcon icon)
        {
            _lblMessage.Text = message;
            Text = title;
            InitButtons(buttons);
            InitIcon(icon);
            Size = MessageSize(message);
        }

        void InitButtons(MessageBoxButtons? buttons)
        {
            if (!buttons.HasValue)
                return;

            switch (buttons)
            {
                case MessageBoxButtons.AbortRetryIgnore:
                    AddButton("Ignore");
                    AddButton("Retry");
                    AddButton("Abort");
                    break;

                case MessageBoxButtons.OK:
                    AddButton("OK");
                    break;

                case MessageBoxButtons.OKCancel:
                    AddButton("Cancel");
                    AddButton("OK");
                    break;

                case MessageBoxButtons.RetryCancel:
                    AddButton("Cancel");
                    AddButton("Retry");
                    break;

                case MessageBoxButtons.YesNo:
                    AddButton("No");
                    AddButton("Yes");
                    break;

                case MessageBoxButtons.YesNoCancel:
                    AddButton("Cancel");
                    AddButton("No");
                    AddButton("Yes");
                    break;
            }
        }

        void InitIcon(MessageBoxIcon icon)
        {
            switch (icon)
            {
                case MessageBoxIcon.None:
                    _picIcon.Hide();
                    break;
                case MessageBoxIcon.Exclamation:
                    _picIcon.Image = SystemIcons.Exclamation.ToBitmap();
                    break;

                case MessageBoxIcon.Error:
                    _picIcon.Image = SystemIcons.Error.ToBitmap();
                    break;

                case MessageBoxIcon.Information:
                    _picIcon.Image = SystemIcons.Information.ToBitmap();
                    break;

                case MessageBoxIcon.Question:
                    _picIcon.Image = SystemIcons.Question.ToBitmap();
                    break;
            }

            _picIcon.Width = _picIcon.Image.Width;
            _picIcon.Height = _picIcon.Image.Height;
        }

        private void ButtonClick(object sender, EventArgs e)
        {
            Button btn = (Button)sender;

            switch (btn.Text)
            {
                case "Abort":
                    DialogResult = DialogResult.Abort;
                    break;

                case "Retry":
                    DialogResult = DialogResult.Retry;
                    break;

                case "Ignore":
                    DialogResult = DialogResult.Ignore;
                    break;

                case "OK":
                    DialogResult = DialogResult.OK;
                    break;

                case "Cancel":
                    DialogResult = DialogResult.Cancel;
                    break;

                case "Yes":
                    DialogResult = DialogResult.Yes;
                    break;

                case "No":
                    DialogResult = DialogResult.No;
                    break;
            }

            Close();
        }

        private static Size MessageSize(string message)
        {
            int width=350;
            int height = 230;

            SizeF size = TextRenderer.MeasureText(message, new Font("Segoe UI", 10));

            if (message.Length < 150)
            {
                if ((int)size.Width > 350)
                {
                    width = (int)size.Width;
                }
            }
            else
            {
                string[] groups = (from Match m in Regex.Matches(message, ".{1,180}") select m.Value).ToArray();
                int lines = groups.Length+1;
                width = 700;
                height += (int)(size.Height+10) * lines;
            }
            return new Size(width, height);
        }

        private void AddButton(string caption)
        {
            var btn = new Button();
            btn.Text = caption;
            btn.Font = new Font("Segoe UI", 8);
            btn.BackColor = Color.FromArgb(225, 225, 225);
            btn.Padding = new Padding(3);
            btn.Height = 30;
            btn.Click += ButtonClick;
            _flpButtons.Controls.Add(btn);
        }
    }
}

One can then just keep the reference of the dialog in a class scope, show the dialog and get the result, or just close it in an application exit event handler.

MsgBox _msgBox;

void eventHandler1(object sender, EventArgs e)
{
    _msgBox = MsgBox.Create("Do you want to continue", "Inquiry", MessageBoxButtons.YesNo);
    var result = _msgBox.ShowDialog();
    // do something with result
}

void applicationExitHandler(object sender, EventArgs e)
{
    if (_msgBox != null)
        _msgBox.Close();
}
Moorings answered 29/9, 2019 at 14:45 Comment(2)
ok, but this solution is mimicking a MessageBox, and thus, it is missing functionality of the original one.Sachiko
@SQLPolice It's still a robust starting point to add anything missing or customize aspect. Also sometimes one need features that original MessageBox doesn't have, hence the usefulness of making a custom MessageBox class. On other SO answers I also found some cool solutions about using the IWin32Window owner parameter of MessageBox.Show() to plug a low level invisible window. I liked that approach also but I couldn't make it robust enough for my needs in short time.Moorings
S
2

I think the cleanest way would be to implement you own message box form like

class MyMessageBox : Form {
  private MyMessageBox currentForm; // The currently active message box

  public static Show(....) { // same as MessageBox.Show
    // ...
  }

  public static Show(...) { // define additional overloads
  }

  public static CloseCurrent() {
    if (currentForm != null)
      currentForm.Close();
  }

  // ...
}

In some of my larger projects, I found this approach useful also for other purposes (such as automatic logging of error messages etc.)

The second idea I have would be to use GetTopWindow() (or maybe some other WIN32 function) to get the current top-level window of your application and send a WM_CLOSE message to it.

Stilla answered 28/10, 2011 at 7:13 Comment(1)
what if the user press alt tab or an other message box shown above the desired message box. in that scenario i guess it will close the undesired one.Disinfectant
M
1

Taking as an assumption that you can edit the code that's calling the MessageBox.Show() method, I would recommend not use MessageBox. Instead, just use your own custom form, calling ShowDialog() on it to do basically the same thing as the MessageBox class. Then, you have the instance of the form itself, and you can call Close() on that instance to close it.

A good example is here.

Morass answered 28/10, 2011 at 7:10 Comment(2)
Ouch. And we see plenty of times how horrible it is when people reimplement message boxes. msg shows that you can have a real message box and close it after a delay ...Teutonism
@Joey: yes, you're right... that was just an idea, not the best probably. You say msg shows that you can have a real message box and close it after a delay ... what or who is "msg"? Sorry, I don't understand, I beg your pardonMorass
H
1

I used .net 2 and two approaches with the same trick.

Open the MessageBox from stub-Form with MessageBox.Show(this,"message")

When the form is not visible or doesn't has really UI.

  1. Keep the form handler and close it with:

    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    

    or

  2. holding the form as class parameter and using FormX.Close().

Since the Form is the owner of the MessageBox, Closing it will close the MessageBox.

Hedda answered 11/4, 2013 at 11:54 Comment(0)
R
1

The easiest solution is to create a form that will close on timer_tick

private int interval = 0;
private string message = "";

public msgBox(string msg = "", int i = 0)
{
    InitializeComponent();
    interval = i;
    message = msg;
}

private void MsgBox_Load(object sender, EventArgs e)
{
    if (interval > 0)
        timer1.Interval = interval;

    lblMessage.Text = message;
    lblMessage.Width = panel1.Width - 20;
    lblMessage.Left = 10;
}

private void Timer1_Tick(object sender, EventArgs e)
{
    this.Close();
}

private void Panel1_Paint(object sender, PaintEventArgs e)
{
    ControlPaint.DrawBorder(e.Graphics, this.panel1.ClientRectangle, Color.DarkBlue, ButtonBorderStyle.Solid);
}

Method to use in main form

   private void showMessage(string msg, int interval = 0)
    {
        msgBox mb = new msgBox(msg, interval);
        mb.ShowDialog(this);
    }

Call it

  showMessage("File saved");
Recusant answered 12/11, 2019 at 9:9 Comment(0)
S
0

Create your own control for this and implement behavior you like to have there. As an option there may be a timer to close this MessageBox.

Stringboard answered 28/10, 2011 at 7:15 Comment(0)
H
0

I figured out a simple Solution, that does not require implementing any additional classes or methods.

// create an invisible Form to be the Owner of the MessageBox
Form myMessageBox = new Form() {
   Owner = MainWindow, // in case you have a main application window
   Size = new System.Drawing.Size(0, 0)
};

MainWindow.BeginInvoke(
    new Action(() =>
    {
        MessageBox.Show(
            myMessageBox, //this is key, here you pass the myMessageBox as owner of the Messagebox
            "The Message",
            "The Window Title",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information,
            MessageBoxDefaultButton.Button1
        );
    }
));


if(messageBoxShallBeClosed)
{
    // whenever or whereever you want to close the MessageBox execute the following
    MainWindow.Invoke(
        new Action(() =>
        {
            myMessageBox.Close(); // this closes the MessageBox, since it is the owner of it
        }
    ));
}

Higher answered 12/4, 2023 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.