Cerrar un cuadro de mensaje después de varios segundos

82

Tengo una aplicación de Windows Forms VS2010 C # donde muestro un cuadro de mensaje para mostrar un mensaje.

Tengo un botón correcto, pero si se alejan, quiero que se agote el tiempo de espera y cerrar el cuadro de mensaje después de digamos 5 segundos, cerrar automáticamente el cuadro de mensaje.

Hay MessageBox personalizados (heredados de Form) u otros Formularios de reportero, pero sería interesante que no fuera necesario un Formulario.

¿Alguna sugerencia o muestra al respecto?

Actualizado:

Para WPF,
cerrar automáticamente el cuadro de mensaje en C #

Cuadro de mensaje personalizado (usando la herencia de formulario)
http://www.codeproject.com/Articles/17253/A-Custom-Message-Box

http://www.codeproject.com/Articles/327212/Custom-Message-Box-in-VC

http://tutplusplus.blogspot.com.es/2010/07/c-tutorial-create-your-own-custom.html

http://medmondson2011.wordpress.com/2010/04/07/easy-to-use-custom-c-message-box-with-a-configurable-checkbox/

Cuadro de mensaje desplazable
Un cuadro de mensaje desplazable en C #

Reportero de excepciones
/programming/49224/good-crash-reporting-library-in-c-sharp

http://www.codeproject.com/Articles/6895/A-Reusable-Flexible-Error-Reporting-Framework

Solución:

Tal vez creo que las siguientes respuestas son una buena solución, sin usar un formulario.

https://stackoverflow.com/a/14522902/206730
https://stackoverflow.com/a/14522952/206730

Kiquenet
fuente
1
Eche un vistazo a esto (Windows Phone, pero debería ser el mismo): stackoverflow.com/questions/9674122/…
jAC
6
@istepaniuk no puede intentarlo si no lo sabe. así que deja ese tipo de preguntas
Mustafa Ekici
1
Debería poder crear un temporizador y configurarlo para que se cierre después de un período de tiempo establecido
Steven Ackley
2
Puede crear el formulario como unMessageBox
spajce
2
@MustafaEkici, estaba invitando al OP a mostrar lo que ha intentado. Supongo que debe haberlo intentado y fallado antes de preguntar en SO. Es por eso que Ramhound y yo rechazamos la pregunta. Puede leer meta.stackexchange.com/questions/122986/…
istepaniuk

Respuestas:

123

Pruebe el siguiente enfoque:

AutoClosingMessageBox.Show("Text", "Caption", 1000);

Donde la AutoClosingMessageBoxclase se implementó de la siguiente manera:

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);
        using(_timeoutTimer)
            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("#32770", _caption); // lpClassName is #32770 for MessageBox
        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);
}

Actualización: si desea obtener el valor de retorno del cuadro de mensaje subyacente cuando el usuario selecciona algo antes del tiempo de espera, puede usar la siguiente versión de este código:

var userResult = AutoClosingMessageBox.Show("Yes or No?", "Caption", 1000, MessageBoxButtons.YesNo);
if(userResult == System.Windows.Forms.DialogResult.Yes) { 
    // do something
}
...
public class AutoClosingMessageBox {
    System.Threading.Timer _timeoutTimer;
    string _caption;
    DialogResult _result;
    DialogResult _timerResult;
    AutoClosingMessageBox(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None) {
        _caption = caption;
        _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
            null, timeout, System.Threading.Timeout.Infinite);
        _timerResult = timerResult;
        using(_timeoutTimer)
            _result = MessageBox.Show(text, caption, buttons);
    }
    public static DialogResult Show(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None) {
        return new AutoClosingMessageBox(text, caption, timeout, buttons, timerResult)._result;
    }
    void OnTimerElapsed(object state) {
        IntPtr mbWnd = FindWindow("#32770", _caption); // lpClassName is #32770 for MessageBox
        if(mbWnd != IntPtr.Zero)
            SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        _timeoutTimer.Dispose();
        _result = _timerResult;
    }
    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);
}

Otra actualización más

Revisé el caso de @ Jack con YesNobotones y descubrí que el enfoque con el envío del WM_CLOSEmensaje no funciona en absoluto.
Proporcionaré una solución en el contexto de la biblioteca AutoclosingMessageBox separada . Esta biblioteca contiene un enfoque rediseñado y, creo, puede ser útil para alguien.
También está disponible a través del paquete NuGet :

Install-Package AutoClosingMessageBox

Notas de la versión (v1.0.0.2):
- Nueva API Show (IWin32Owner) para admitir los escenarios más populares (en el contexto del n . ° 1 );
- API de New Factory () para proporcionar un control total sobre la visualización de MessageBox;

DmitryG
fuente
¿Mejor usar System.Threading.Timer o System.Timers.Timer (como la respuesta de @Jens)? SendMessage vs PostMessage?
Kiquenet
@Kiquenet Creo que no hay diferencias significativas en esta situación específica.
DmitryG
1
@GeorgeBirbilis Gracias, puede tener sentido ... En este caso, puede usar #32770valor como nombre de clase
DmitryG
2
No me funciona si los botones estánSystem.Windows.Forms.MessageBoxButtons.YesNo
Jack
1
@Jack Perdón por la respuesta tardía, he comprobado el caso con YesNobotones, tienes toda la razón, no funciona. Proporcionaré una solución en el contexto de la biblioteca AutoclosingMessageBox separada . Contiene el enfoque rediseñado y creo que puede ser útil. ¡Gracias!
DmitryG
32

Una solución que funciona en WinForms:

var w = new Form() { Size = new Size(0, 0) };
Task.Delay(TimeSpan.FromSeconds(10))
    .ContinueWith((t) => w.Close(), TaskScheduler.FromCurrentSynchronizationContext());

MessageBox.Show(w, message, caption);

Basado en el efecto de que cerrar el formulario que posee el cuadro de mensaje también cerrará el cuadro.

Los controles de Windows Forms tienen el requisito de que se debe acceder a ellos en el mismo subproceso que los creó. El uso TaskScheduler.FromCurrentSynchronizationContext()garantizará eso, asumiendo que el código de ejemplo anterior se ejecuta en el hilo de la interfaz de usuario o en un hilo creado por el usuario. El ejemplo no funcionará correctamente si el código se ejecuta en un hilo de un grupo de hilos (por ejemplo, una devolución de llamada del temporizador) o un grupo de tareas (por ejemplo, en una tarea creada con TaskFactory.StartNewo Task.Runcon parámetros predeterminados).

BSharp
fuente
¿Qué es la versión .NET? ¿Qué es TaskScheduler?
Kiquenet
1
@Kiquenet .NET 4.0 y versiones posteriores. Tasky TaskSchedulerson del espacio de nombres System.Threading.Tasksen mscorlib.dll, por lo que no se necesitan referencias de ensamblado adicionales.
BSharp
¡Gran solución! Una adición ... esto funcionó bien en una aplicación Winforms, DESPUÉS de agregar BringToFront para el nuevo formulario, justo después de crearlo. De lo contrario, los cuadros de diálogo a veces se mostraban detrás del formulario activo actual, es decir, no aparecían para el usuario. var w = new Form () {Size = new Size (0, 0)}; w.BringToFront ();
Developer63
@ Developer63 No pude reproducir su experiencia. Incluso al llamar w.SentToBack()justo antes MessageBox.Show(), el cuadro de diálogo todavía se mostraba en la parte superior del formulario principal. Probado en .NET 4.5 y 4.7.1.
BSharp
Esta solución podría ser buena, pero solo está disponible desde .NET 4.5 y versiones posteriores, no 4.0 (debido a Task.Delay) ver: stackoverflow.com/questions/17717047/…
KwentRell
16

AppActivate!

Si no le importa enturbiar un poco sus referencias, puede incluir Microsoft.Visualbasic,y utilizar esta forma muy breve.

Mostrar el cuadro de mensaje

    (new System.Threading.Thread(CloseIt)).Start();
    MessageBox.Show("HI");

Función CloseIt:

public void CloseIt()
{
    System.Threading.Thread.Sleep(2000);
    Microsoft.VisualBasic.Interaction.AppActivate( 
         System.Diagnostics.Process.GetCurrentProcess().Id);
    System.Windows.Forms.SendKeys.SendWait(" ");
}

¡Ahora ve a lavarte las manos!

FastAl
fuente
11

El método System.Windows.MessageBox.Show () tiene una sobrecarga que toma una ventana propietaria como primer parámetro. Si creamos una ventana de propietario invisible que luego cerramos después de un tiempo específico, su cuadro de mensaje secundario también se cerraría.

Window owner = CreateAutoCloseWindow(dialogTimeout);
MessageBoxResult result = MessageBox.Show(owner, ...

Hasta aquí todo bien. Pero, ¿cómo cerramos una ventana si el hilo de la interfaz de usuario está bloqueado por el cuadro de mensaje y no se puede acceder a los controles de la interfaz de usuario desde un hilo de trabajo? La respuesta es: enviando un mensaje de Windows WM_CLOSE al identificador de la ventana del propietario:

Window CreateAutoCloseWindow(TimeSpan timeout)
{
    Window window = new Window()
    {
        WindowStyle = WindowStyle.None,
        WindowState = System.Windows.WindowState.Maximized,
        Background =  System.Windows.Media.Brushes.Transparent, 
        AllowsTransparency = true,
        ShowInTaskbar = false,
        ShowActivated = true,
        Topmost = true
    };

    window.Show();

    IntPtr handle = new WindowInteropHelper(window).Handle;

    Task.Delay((int)timeout.TotalMilliseconds).ContinueWith(
        t => NativeMethods.SendMessage(handle, 0x10 /*WM_CLOSE*/, IntPtr.Zero, IntPtr.Zero));

    return window;
}

Y aquí está la importación para el método API de Windows SendMessage:

static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
Esge
fuente
¿El tipo de ventana es para Windows Forms?
Kiquenet
¿Por qué necesita enviar un mensaje a la ventana principal oculta para cerrarla? ¿No puedes simplemente llamar a algún método "Cerrar" o desecharlo de otra manera?
George Birbilis
Para responder a mi propia pregunta, la propiedad OwnWindows de esa ventana de WPF parece mostrar 0 ventanas y Cerrar no cierra el cuadro secundario del
cuadro de
2
Solución brillante. Hay cierta superposición de nombres System.Windowsy System.Windows.Formseso me tomó un tiempo averiguarlo. Necesitará la siguiente: System, System.Runtime.InteropServices, System.Threading.Tasks, System.Windows, System.Windows.Interop,System.Windows.Media
m3tikn0b
10

Puedes probar esto:

[DllImport("user32.dll", EntryPoint="FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

[DllImport("user32.Dll")]
static extern int PostMessage(IntPtr hWnd, UInt32 msg, int wParam, int lParam);

private const UInt32 WM_CLOSE = 0x0010;

public void ShowAutoClosingMessageBox(string message, string caption)
{
    var timer = new System.Timers.Timer(5000) { AutoReset = false };
    timer.Elapsed += delegate
    {
        IntPtr hWnd = FindWindowByCaption(IntPtr.Zero, caption);
        if (hWnd.ToInt32() != 0) PostMessage(hWnd, WM_CLOSE, 0, 0);
    };
    timer.Enabled = true;
    MessageBox.Show(message, caption);
}
Jens Granlund
fuente
2
¿Mejor usar System.Threading.Timer o System.Timers.Timer (como la respuesta de @DmitryG)? SendMessage vs PostMessage?
Kiquenet
1
consulte stackoverflow.com/questions/3376619/… y tal vez también stackoverflow.com/questions/2411116/… sobre la diferencia entre SendMessage y PostMessage
George Birbilis
7

RogerB en CodeProject tiene una de las soluciones más hábiles para esta respuesta, y lo hizo en '04, y todavía está funcionando

Básicamente, vaya aquí a su proyecto y descargue el archivo CS . En caso que enlazan muere nunca, tengo una copia de seguridad lo esencial aquí. Agregue el archivo CS a su proyecto o copie / pegue el código en algún lugar si prefiere hacerlo.

Entonces, todo lo que tendrías que hacer es cambiar

DialogResult result = MessageBox.Show("Text","Title", MessageBoxButtons.CHOICE)

a

DialogResult result = MessageBoxEx.Show("Text","Title", MessageBoxButtons.CHOICE, timer_ms)

Y estás listo para irte.

kayleeFrye_onDeck
fuente
2
¡Necesitaba una solución rápida y funcionó muy bien! Gracias por compartir.
Mark Kram
1
Te perdiste la extensión .Show en tus ejemplos ... Debería leer: DialogResult result = MessageBoxEx.Show ("Text", "Title", MessageBoxButtons.CHOICE, timer_ms)
Edd
2

Hay un proyecto de codeproject disponible AQUÍ que proporciona esta funcionalidad.

Siguiendo muchos hilos aquí en SO y otros tableros, esto no se puede hacer con el MessageBox normal.

Editar:

Tengo una idea que es un poco ehmmm, sí ...

Use un temporizador y comience cuando aparezca el cuadro de mensaje. Si su cuadro de mensajes solo escucha el botón OK (solo 1 posibilidad), use el evento OnTick para emular un ESC-Presione SendKeys.Send("{ESC}");y luego detenga el temporizador.

jAC
fuente
1
El concepto de temporizador es una forma simple ... pero debe asegurarse de que las claves enviadas lleguen a su aplicación si no tiene o pierde el enfoque. Eso requeriría SetForegroundWindow y la respuesta comienza a incluir más código, pero vea 'AppActivate' a continuación.
FastAl
2

El código de DMitryG "obtiene el valor de retorno del subyacente MessageBox" tiene un error, por lo que timerResult nunca se devuelve correctamente (la MessageBox.Showllamada regresa DESPUÉS de que se OnTimerElapsedcomplete). Mi solución está a continuación:

public class TimedMessageBox {
    System.Threading.Timer _timeoutTimer;
    string _caption;
    DialogResult _result;
    DialogResult _timerResult;
    bool timedOut = false;

    TimedMessageBox(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None)
    {
        _caption = caption;
        _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
            null, timeout, System.Threading.Timeout.Infinite);
        _timerResult = timerResult;
        using(_timeoutTimer)
            _result = MessageBox.Show(text, caption, buttons);
        if (timedOut) _result = _timerResult;
    }

    public static DialogResult Show(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None) {
        return new TimedMessageBox(text, caption, timeout, buttons, timerResult)._result;
    }

    void OnTimerElapsed(object state) {
        IntPtr mbWnd = FindWindow("#32770", _caption); // lpClassName is #32770 for MessageBox
        if(mbWnd != IntPtr.Zero)
            SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        _timeoutTimer.Dispose();
        timedOut = true;
    }

    const int WM_CLOSE = 0x0010;
    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    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);
}
En la web
fuente
1

La biblioteca Vb.net tiene una solución simple que usa la clase de interacción para esto:

void MsgPopup(string text, string title, int secs = 3)
{
    dynamic intr = Microsoft.VisualBasic.Interaction.CreateObject("WScript.Shell");
    intr.Popup(text, secs, title);
}

bool MsgPopupYesNo(string text, string title, int secs = 3)
{
    dynamic intr = Microsoft.VisualBasic.Interaction.CreateObject("WScript.Shell");
    int answer = intr.Popup(text, secs, title, (int)Microsoft.VisualBasic.Constants.vbYesNo + (int)Microsoft.VisualBasic.Constants.vbQuestion);
    return (answer == 6);
}
Cenk
fuente
0

Hay una API no documentada en user32.dll llamada MessageBoxTimeout () pero requiere Windows XP o posterior.

Greg Wittmeyer
fuente
0

usar en EndDialoglugar de enviar WM_CLOSE:

[DllImport("user32.dll")]
public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);
Kaven Wu
fuente
0

Lo hice asi

var owner = new Form { TopMost = true };
Task.Delay(30000).ContinueWith(t => {
owner.Invoke(new Action(()=>
{
      if (!owner.IsDisposed)
      {
          owner.Close();
      }
   }));
});
var dialogRes =  MessageBox.Show(owner, msg, "Info", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
santipianis
fuente