Schließen Sie eine MessageBox nach einigen Sekunden

82

Ich habe eine Windows Forms-Anwendung VS2010 C #, in der ich eine MessageBox zum Anzeigen einer Nachricht anzeige.

Ich habe eine OK-Schaltfläche, aber wenn sie weggehen, möchte ich eine Zeitüberschreitung feststellen und das Nachrichtenfeld schließen, nachdem wir beispielsweise 5 Sekunden vergangen sind. Schließen Sie das Nachrichtenfeld automatisch.

Es gibt benutzerdefinierte MessageBox (die von Form geerbt wurden) oder andere Reporterformulare, aber es wäre interessant, kein Formular zu benötigen.

Anregungen oder Beispiele dazu?

Aktualisiert:

Für WPF
Nachrichtenbox in C # automatisch schließen

Benutzerdefinierte MessageBox (mithilfe der Formularvererbung)
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/

Scrollbare MessageBox
Eine scrollbare MessageBox in C #

Exception Reporter
/programming/49224/good-crash-reporting-library-in-c-sharp

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

Lösung:

Vielleicht denke ich, dass die folgenden Antworten eine gute Lösung sind, ohne ein Formular zu verwenden.

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

Kiquenet
quelle
1
Schauen Sie sich das an (Windows Phone, sollte aber gleich sein): stackoverflow.com/questions/9674122/…
jAC
6
@istepaniuk er kann es nicht versuchen, wenn er es nicht weiß. Also hör
Mustafa Ekici
1
Sie sollten in der Lage sein, einen Timer zu erstellen und ihn so einzustellen, dass er nach einer festgelegten Zeit geschlossen wird
Steven Ackley,
2
Sie können das Formular alsMessageBox
spajce
2
@MustafaEkici, ich habe das OP eingeladen, um zu zeigen, was er versucht hat. Ich nehme an, er muss es versucht haben und ist gescheitert, bevor er tatsächlich in SO gefragt hat. Deshalb haben Ramhound und ich die Frage abgelehnt. Sie können meta.stackexchange.com/questions/122986/…
istepaniuk

Antworten:

123

Versuchen Sie den folgenden Ansatz:

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

Wo die AutoClosingMessageBoxKlasse wie folgt implementiert wurde:

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

Update: Wenn Sie den Rückgabewert der zugrunde liegenden MessageBox erhalten möchten, wenn der Benutzer vor dem Timeout etwas auswählt, können Sie die folgende Version dieses Codes verwenden:

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

Noch ein Update

Ich habe den Fall von @ Jack mit YesNoSchaltflächen überprüft und festgestellt, dass der Ansatz beim Senden der WM_CLOSENachricht überhaupt nicht funktioniert.
Ich werde einen Fix im Kontext der separaten AutoclosingMessageBox- Bibliothek bereitstellen . Diese Bibliothek enthält einen neu gestalteten Ansatz und kann meiner Meinung nach für jemanden nützlich sein.
Es ist auch über das NuGet-Paket erhältlich :

Install-Package AutoClosingMessageBox

Versionshinweise (v1.0.0.2):
- Neue Show (IWin32Owner) -API zur Unterstützung der gängigsten Szenarien (im Kontext von # 1 );
- Neue Factory () - API zur vollständigen Kontrolle der Anzeige von MessageBox;

DmitryG
quelle
Besser System.Threading.Timer oder System.Timers.Timer verwenden (wie @Jens Antwort)? SendMessage vs PostMessage?
Kiquenet
@Kiquenet Ich glaube, es gibt keine signifikanten Unterschiede in dieser speziellen Situation.
DmitryG
1
@ GeorgeBirbilis Danke, es kann einen Sinn ergeben ... In diesem Fall können Sie #32770Wert als Klassennamen verwenden
DmitryG
2
Es funktioniert nicht für mich, wenn Knöpfe sindSystem.Windows.Forms.MessageBoxButtons.YesNo
Jack
1
@ Jack Entschuldigung für die späte Antwort, ich habe den Fall mit YesNoKnöpfen überprüft - Sie sind absolut korrekt - es funktioniert nicht. Ich werde im Kontext der separaten AutoclosingMessageBox- Bibliothek einen Fix bereitstellen . Das enthält den neu gestalteten Ansatz und kann meiner Meinung nach nützlich sein. Vielen Dank!
DmitryG
31

Eine Lösung, die in WinForms funktioniert:

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

Basierend auf dem Effekt, dass das Schließen des Formulars, dem das Nachrichtenfeld gehört, das Feld ebenfalls schließt.

Für Windows Forms-Steuerelemente muss auf denselben Thread zugegriffen werden, mit dem sie erstellt wurden. Mit TaskScheduler.FromCurrentSynchronizationContext()wird sichergestellt, dass der obige Beispielcode auf dem UI-Thread oder einem vom Benutzer erstellten Thread ausgeführt wird. Das Beispiel funktioniert nicht richtig, wenn der Code in einem Thread aus einem Thread-Pool (z. B. einem Timer-Rückruf) oder einem Task-Pool (z. B. in einer Task, die mit TaskFactory.StartNewoder Task.Runmit Standardparametern erstellt wurde) ausgeführt wird.

BSharp
quelle
Was ist die Version .NET? Was ist es TaskScheduler?
Kiquenet
1
@Kiquenet .NET 4.0 und höher. Taskund TaskSchedulerstammen aus dem Namespace System.Threading.Tasksin mscorlib.dll, sodass keine zusätzlichen Assemblyreferenzen erforderlich sind.
BSharp
Tolle Lösung! Ein Zusatz ... dies funktionierte gut in einer Winforms-App, nachdem BringToFront für das neue Formular direkt nach dem Erstellen hinzugefügt wurde. Andernfalls wurden die Dialogfelder manchmal hinter dem aktuell aktiven Formular angezeigt, dh sie wurden dem Benutzer nicht angezeigt. var w = neue Form () {Größe = neue Größe (0, 0)}; w.BringToFront ();
Developer63
@ Developer63 Ich konnte deine Erfahrung nicht reproduzieren. Selbst wenn Sie w.SentToBack()direkt vorher anrufen MessageBox.Show(), wird das Dialogfeld immer noch über dem Hauptformular angezeigt. Getestet unter .NET 4.5 und 4.7.1.
BSharp
Diese Lösung könnte nett sein, ist aber nur ab .NET 4.5 und höher verfügbar, nicht 4.0 (wegen Task.Delay). Siehe: stackoverflow.com/questions/17717047/…
KwentRell
16

AppActivate!

Wenn es Ihnen nichts ausmacht, Ihre Referenzen ein wenig zu verwirren, können Sie Microsoft.Visualbasic,diesen sehr kurzen Weg einschließen und verwenden.

Zeigen Sie die MessageBox an

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

CloseIt-Funktion:

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

Jetzt geh und wasche deine Hände!

FastAl
quelle
11

Die System.Windows.MessageBox.Show () -Methode weist eine Überladung auf, bei der ein Eigentümerfenster als erster Parameter verwendet wird. Wenn wir ein unsichtbares Eigentümerfenster erstellen, das wir nach einer bestimmten Zeit schließen, wird auch das untergeordnete Meldungsfeld geschlossen.

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

So weit, ist es gut. Aber wie schließen wir ein Fenster, wenn der UI-Thread durch das Meldungsfeld blockiert wird und auf UI-Steuerelemente nicht über einen Worker-Thread zugegriffen werden kann? Die Antwort lautet: Durch Senden einer WM_CLOSE-Fensternachricht an das Eigentümerfenster-Handle:

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

Und hier ist der Import für die SendMessage Windows API-Methode:

static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
Esge
quelle
Fenstertyp ist für Windows Forms?
Kiquenet
Warum müssen Sie eine Nachricht an das ausgeblendete übergeordnete Fenster senden, um es zu schließen? Können Sie nicht einfach eine "Close" -Methode aufrufen oder anderweitig entsorgen?
George Birbilis
Um meine eigene Frage zu beantworten, scheint die OwnedWindows-Eigenschaft dieses WPF-Fensters 0 Fenster anzuzeigen, und Close schließt das
untergeordnete Nachrichtenfeld
2
Brillante Lösung. Es gibt einige Namensüberschneidungen, System.Windowsund System.Windows.Formsich habe einige Zeit gebraucht, um das herauszufinden. Sie müssen die folgenden: System, System.Runtime.InteropServices, System.Threading.Tasks, System.Windows, System.Windows.Interop,System.Windows.Media
m3tikn0b
10

Sie könnten dies versuchen:

[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
quelle
2
Besser System.Threading.Timer oder System.Timers.Timer verwenden (wie @DmitryG Antwort)? SendMessage vs PostMessage?
Kiquenet
1
Siehe stackoverflow.com/questions/3376619/… und möglicherweise auch stackoverflow.com/questions/2411116/… zum Unterschied zwischen SendMessage und PostMessage
George Birbilis
7

RogerB bei CodeProject hat eine der cleversten Lösungen für diese Antwort, und das hat er bereits im Jahr 2004 getan, und es ist immer noch großartig.

Grundsätzlich gehen Sie hier zu seinem Projekt und laden die CS-Datei herunter . Für den Fall, dass dieser Link jemals stirbt, habe ich hier einen Backup- Kern . Fügen Sie die CS-Datei zu Ihrem Projekt hinzu oder kopieren Sie den Code irgendwo, wenn Sie dies lieber tun möchten.

Dann müssten Sie nur noch wechseln

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

zu

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

Und du kannst loslegen.

kayleeFrye_onDeck
quelle
2
Ich brauchte eine schnelle Lösung und das hat super funktioniert! Danke für das Teilen.
Mark Kram
1
Sie haben die Erweiterung .Show in Ihren Beispielen verpasst ... Sollte lauten: DialogResult result = MessageBoxEx.Show ("Text", "Titel", MessageBoxButtons.CHOICE, timer_ms)
Edd
2

HIER ist ein Codeprojekt-Projekt verfügbar , das diese Funktionalität bietet.

Nach vielen Threads hier auf SO und anderen Boards kann dies nicht mit der normalen MessageBox durchgeführt werden.

Bearbeiten:

Ich habe eine Idee, die ein bisschen ehmmm ist, ja ..

Verwenden Sie einen Timer und starten Sie, wenn die MessageBox angezeigt wird. Wenn Ihre MessageBox nur auf die Schaltfläche OK hört (nur 1 Möglichkeit), emulieren Sie mit dem OnTick-Ereignis eine ESC-Presse mit SendKeys.Send("{ESC}");und stoppen Sie dann den Timer.

jAC
quelle
1
Das Timer-Konzept ist ein einfacher Weg ... aber Sie müssen sicherstellen, dass die gesendeten Schlüssel Ihre App treffen, wenn sie den Fokus nicht hat oder verliert. Dies würde SetForegroundWindow erfordern und die Antwort enthält mehr Code, siehe jedoch 'AppActivate' weiter unten.
FastAl
2

Der Code von DMitryG "Rückgabewert des Basiswerts MessageBoxabrufen" weist einen Fehler auf, sodass das timerResult nie korrekt zurückgegeben wird ( MessageBox.ShowAufruf wird nach OnTimerElapsedAbschluss zurückgegeben). Mein Fix ist unten:

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);
}
OnWeb
quelle
1

Die Vb.net-Bibliothek bietet hierfür eine einfache Lösung mit Interaktionsklasse:

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
quelle
0

In user32.dll gibt es eine undokumentierte API mit dem Namen MessageBoxTimeout (), für die jedoch Windows XP oder höher erforderlich ist.

Greg Wittmeyer
quelle
0

Verwenden Sie EndDialogstatt zu senden WM_CLOSE:

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

Ich habe es so gemacht

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
quelle