Принудительно закрыть MessageBox

Позвольте мне дать вам фон.

У нас есть приложение (среднего размера), которое использует MessageBox.Show(....) в разных местах (сотнями).

Эти окна сообщений являются частью рабочего процесса и используются для информирования, предупреждения или получения информации от пользователя. Приложение должно автоматически выходить из системы по истечении определенного времени, если нет активности. У нас есть требование, чтобы при выходе из приложения просто очистить данные сеанса, очистить представления и спрятать себя, чтобы при следующем запуске не пришлось выполнять процесс запуска, который является дорогостоящим с точки зрения времени.

Все работает нормально, но в сценарии, когда на экране есть какое-то окно сообщения, и пользователь покинул машину, не отвечая на окно сообщения, а затем из-за отсутствия активности, чтобы заставить приложение выйти из системы. Проблема в том, что окно сообщения не исчезнет.

Как я могу закрыть открытое окно сообщения, если оно есть, скрывая приложение?

Ответы

Ответ 1

Вот фрагмент кода, основанный на UIAutomation (классный, но все еще не очень используемый API), который пытается закрыть все модальные окна (в том числе тот, который открыт с MessageBox) текущего процесса:

    /// <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 ready, try to close it
                    WindowPattern childWindow = (WindowPattern)pattern;
                    if (childWindow.Current.WindowInteractionState == WindowInteractionState.ReadyForUserInteraction)
                    {
                        childWindow.Close();
                    }
                }
            }
        }
    }

Например, если у вас есть приложение WinForms, которое выдает MessageBox при нажатии некоторой кнопки1, вы все равно сможете закрыть приложение, используя меню "Закрыть окно" Windows (щелкните правой кнопкой мыши на панели задач):

    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);
    }

Вы можете использовать CloseModalWindows где-то еще в своем коде, конечно, это всего лишь образец.

Ответ 2

Эта ссылка на форумах MSDN показывает, как закрыть окно сообщения, используя FindWindow и отправив сообщение WM_CLOSE. Хотя вопрос был задан для .NET/WindowsCE, он может решить вашу проблему, стоит посмотреть

Ответ 3

Сначала вопрос: если ящики сообщений используются как часть рабочего процесса, не будет ли программно закрывать окно сообщения, чтобы поток изменился/продолжался?

Я думаю, у вас есть три варианта

  • Создайте собственную версию класса сообщений, которая откроет диалоговое окно, которое выглядит как окно с добавленной функциональностью, поэтому оно автоматически закрывается через некоторое время.

  • Внедрите что-то вроде этого в С#, чтобы закрыть окно сообщений программно. http://www.codeproject.com/KB/dialog/AutoCloseMessageBox.aspx

  • Избавиться от ящиков сообщений от перебоев рабочего процесса. Это, вероятно, лучшее решение, поскольку звук его закрытия окна сообщения программно приведет к продолжению/изменению рабочего процесса и, возможно, даже вызовет отображение другого сообщения, которое может быть нежелательным. Но очевидно, что исправление проблемы с корнем может быть лучше, но не всегда является самым простым.

1 и 2 нужно будет сделать из отдельного потока, поэтому вам нужно будет подумать о последствиях этого, поскольку показ сообщения будет заблокирован.

Ответ 4

Вот мой пример с SendKeys - проверенный и работающий:

скажем, у нас есть фоновая работа и кнопка в форме. После нажатия кнопки - запустите рабочий стол и покажите окно сообщения. В рабочем случае DoWork сон на 5 секунд, а затем отправьте ключ ввода - беспорядок закрыто.

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
}

Ответ 5

Принимая в качестве предположения, что вы можете редактировать код, вызывающий MessageBox.Show(), я бы рекомендовал не использовать Окно сообщения. Вместо этого просто используйте свою собственную форму, вызывая ShowDialog() на нем делать в основном то же самое, что и класс MessageBox. Затем вы иметь экземпляр самой формы, и вы можете вызвать Close() на этом чтобы закрыть его.

Хорошим примером является здесь.

Ответ 6

Я думаю, что самым чистым способом было бы реализовать собственную форму ящика сообщений, например

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();
  }

  // ...
}

В некоторых моих крупных проектах я нашел этот подход полезным и для других целей (например, автоматическое ведение журнала сообщений об ошибках и т.д.)

Вторая идея - использовать GetTopWindow() (или, возможно, какую-то другую функцию WIN32), чтобы получить текущее окно верхнего уровня вашего приложения и отправить ему сообщение WM_CLOSE.

Ответ 7

Я использовал .net 2 и два подхода с тем же трюком.

Откройте MessageBox из заглушки с помощью MessageBox.Show(this,"message")

Если форма не видна или не имеет действительно UI.

  • Сохраните обработчик формы и закройте его:

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

    или

  • удерживая форму как параметр класса и используя FormX.Close().

Поскольку форма является владельцем MessageBox, закрытие закрывает MessageBox.

Ответ 8

Обратитесь к сообщению DmitryG в "Закройте MessageBox через несколько секунд"

Автоматическое закрытие MessageBox после достижения времени ожидания

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);
    }

и вызвать его через

AutoClosingMessageBox.Show("Content", "Title", TimeOut);

Ответ 9

Создайте свой собственный контроль для этого и реализуйте поведение, которое вам нравится. В качестве опции может быть таймер для закрытия этого MessageBox.

Ответ 10

Эта тема широко освещалась в других SO-вопросах, но, поскольку у этого конкретного вопроса есть несколько ответов об использовании методов автоматизации пользовательского интерфейса/поиска окон (которые мне не особенно нравятся) и общие предложения по созданию собственного диалога без предоставленного кода, я решил опубликовать свой собственное решение. Можно создать инстанцируемый MessageBox подобный класс следующим образом:

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);
        }
    }
}

Затем можно просто сохранить ссылку на диалог в области видимости класса, показать диалог и получить результат или просто закрыть его в обработчике события выхода из приложения.

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();
}