Принудительно закрыть 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();
}