Zamknij MessageBox po kilku sekundach

82

Mam aplikację Windows Forms VS2010 C #, w której wyświetlam MessageBox w celu wyświetlenia komunikatu.

Mam przycisk OK, ale jeśli odejdą, chcę przekroczyć limit czasu i zamknąć okno komunikatu po, powiedzmy 5 sekundach, automatycznie zamknij okno komunikatu.

Istnieją niestandardowe MessageBox (dziedziczone z Form) lub inne formularze reporterskie, ale byłoby interesujące, gdyby nie formularz.

Jakieś sugestie lub próbki na ten temat?

Zaktualizowano:

W przypadku WPF
automatycznie zamknij skrzynkę wiadomości w języku C #

Niestandardowe pole wiadomości (przy użyciu dziedziczenia formularzy)
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/

Przewijalny MessageBox
Przewijalny MessageBox w C #

Raport o wyjątkach
/programming/49224/good-crash-reporting-library-in-c-sharp

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

Rozwiązanie:

Może myślę, że poniższe odpowiedzi są dobrym rozwiązaniem bez użycia formularza.

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

Kiquenet
źródło
1
Spójrz na to (Windows Phone, ale powinien być taki sam): stackoverflow.com/questions/9674122/ ...
jAC
6
@istepaniuk nie może spróbować, jeśli nie wie. więc przestań zadawać tego rodzaju pytania
Mustafa Ekici
1
Powinieneś być w stanie stworzyć czasomierz i ustawić go zamknąć po upływie określonego czasu
Steven ACKLEY
2
Formularz można utworzyć jakoMessageBox
spajce
2
@MustafaEkici, zapraszałem OP, aby pokazał, czego próbował. Zakładam, że musiał spróbować i zawiódł, zanim faktycznie zapytał w SO. Dlatego Ramhound i ja odrzuciliśmy to pytanie. Możesz przeczytać meta.stackexchange.com/questions/122986/...
istepaniuk

Odpowiedzi:

123

Wypróbuj następujące podejście:

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

Gdzie AutoClosingMessageBoxklasa zaimplementowana w następujący sposób:

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

Aktualizacja: Jeśli chcesz uzyskać wartość zwracaną podstawowego MessageBox, gdy użytkownik wybierze coś przed upływem limitu czasu, możesz użyć następującej wersji tego kodu:

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

Kolejna aktualizacja

Sprawdziłem obudowę @ Jacka z YesNoprzyciskami i odkryłem, że podejście z wysłaniem WM_CLOSEwiadomości w ogóle nie działa.
Będę dostarczenie poprawki w ramach oddzielnej AutoclosingMessageBox bibliotece. Ta biblioteka zawiera przeprojektowane podejście i, jak sądzę, może być dla kogoś przydatna.
Jest również dostępny za pośrednictwem pakietu NuGet :

Install-Package AutoClosingMessageBox

Uwagi do wydania (v1.0.0.2):
- Nowe API programu Show (IWin32Owner) obsługujące najpopularniejsze scenariusze (w kontekście # 1 );
- New Factory () API zapewniające pełną kontrolę nad wyświetlaniem MessageBox;

DmitryG
źródło
Lepiej używaj System.Threading.Timer lub System.Timers.Timer (np. @Jens answer)? SendMessage vs PostMessage?
Kiquenet
@Kiquenet Uważam, że nie ma znaczących różnic w tej konkretnej sytuacji.
DmitryG
1
@GeorgeBirbilis Dzięki, to może mieć sens ... W tym przypadku możesz użyć #32770value jako nazwy klasy
DmitryG
2
U mnie nie działa, jeśli przyciski sąSystem.Windows.Forms.MessageBoxButtons.YesNo
Jack
1
@Jack Przepraszamy za spóźnioną odpowiedź, sprawdziłem obudowę z YesNoprzyciskami - masz absolutną rację - nie działa. Zapewnię poprawkę w kontekście oddzielnej biblioteki AutoclosingMessageBox . Zawiera przeprojektowane podejście i, jak sądzę, może być przydatne. Dzięki!
DmitryG
32

Rozwiązanie działające w 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);

W oparciu o efekt polegający na tym, że zamknięcie formularza będącego właścicielem okna komunikatu spowoduje również zamknięcie okna.

Kontrolki Windows Forms wymagają, aby były dostępne w tym samym wątku, w którym zostały utworzone. Użycie TaskScheduler.FromCurrentSynchronizationContext()zapewni, że przy założeniu, że powyższy przykładowy kod jest wykonywany w wątku interfejsu użytkownika lub w wątku utworzonym przez użytkownika. Przykład nie zadziała poprawnie, jeśli kod zostanie wykonany na wątku z puli wątków (np. Wywołanie zwrotne licznika czasu) lub puli zadań (np. Na zadaniu utworzonym z TaskFactory.StartNewlub Task.Runz parametrami domyślnymi).

BSharp
źródło
Jaka jest wersja .NET? Co to jest TaskScheduler?
Kiquenet,
1
@Kiquenet .NET 4.0 i nowsze. Taski TaskSchedulerpochodzą z przestrzeni nazw System.Threading.Tasksw mscorlib.dll, więc nie są potrzebne żadne dodatkowe odwołania do zestawu.
BSharp,
Świetne rozwiązanie! Jeden dodatek ... to działało dobrze w aplikacji Winform, PO dodaniu BringToFront dla nowego formularza, zaraz po jego utworzeniu. W przeciwnym razie okna dialogowe czasami pojawiały się za aktualnie aktywnym formularzem, tj. Nie były wyświetlane użytkownikowi. var w = new Form () {Size = new Size (0, 0)}; w.BringToFront ();
Deweloper63
@ Developer63 Nie mogę odtworzyć twoich wrażeń. Nawet podczas wywoływania w.SentToBack()tuż przed MessageBox.Show(), okno dialogowe nadal było wyświetlane u góry głównego formularza. Przetestowano na platformach .NET 4.5 i 4.7.1.
BSharp
To rozwiązanie mogłoby być fajne, ale jest dostępne tylko od .NET 4.5 i nowszych, a nie 4.0 (z powodu Task.Delay) patrz: stackoverflow.com/questions/17717047/…
KwentRell KwentRell
16

AppActivate!

Jeśli nie masz nic przeciwko nieco zamgleniu swoich odniesień, możesz dołączyć Microsoft.Visualbasic,i wykorzystać ten bardzo krótki sposób.

Wyświetl MessageBox

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

Funkcja CloseIt:

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

Teraz idź umyć ręce!

FastAl
źródło
11

Metoda System.Windows.MessageBox.Show () ma przeciążenie, które przyjmuje właściciela Window jako pierwszy parametr. Jeśli utworzymy niewidoczne okno właściciela, które następnie zamkniemy po określonym czasie, jego podrzędne okno komunikatu również zostanie zamknięte.

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

Jak na razie dobrze. Ale jak zamknąć okno, jeśli wątek interfejsu użytkownika jest blokowany przez okno komunikatu, a kontrolki interfejsu użytkownika nie są dostępne z wątku roboczego? Odpowiedź brzmi - wysyłając wiadomość WM_CLOSE okna do uchwytu okna właściciela:

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

A oto import dla metody SendMessage Windows API:

static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
Esge
źródło
Typ okna jest przeznaczony dla Windows Forms?
Kiquenet,
Dlaczego musisz wysłać wiadomość do ukrytego okna nadrzędnego, aby je zamknąć? Nie możesz po prostu wywołać jakiejś metody "Zamknij" lub w inny sposób ją zlikwidować?
George Birbilis
Aby odpowiedzieć na moje własne pytanie, właściwość OwnedWindows tego okna WPF wydaje się pokazywać okna 0, a funkcja Close nie zamyka podrzędnej
skrzynki wiadomości
2
Wspaniałe rozwiązanie. Nazwy częściowo się pokrywają System.Windowsi System.Windows.Formszajęło mi to trochę czasu, zanim to zrozumiałem. Potrzebne będą następujące elementy: System, System.Runtime.InteropServices, System.Threading.Tasks, System.Windows, System.Windows.Interop,System.Windows.Media
m3tikn0b
10

Możesz spróbować tego:

[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
źródło
2
Lepiej użyj System.Threading.Timer lub System.Timers.Timer (jak @DmitryG answer)? SendMessage vs PostMessage?
Kiquenet
1
zobacz stackoverflow.com/questions/3376619/ ... a może także stackoverflow.com/questions/2411116/ ... na temat różnicy między SendMessage a PostMessage
George Birbilis
7

RogerB w CodeProject ma jedno z najsprytniejszych rozwiązań tej odpowiedzi i zrobił to w 2004 roku i nadal jest świetny

Zasadniczo przechodzisz tutaj do jego projektu i pobierasz plik CS . W przypadku, gdy związek umiera nigdy, mam zapasową istotę tutaj. Dodaj plik CS do swojego projektu lub skopiuj / wklej kod gdzieś, jeśli wolisz to zrobić.

Następnie wszystko, co musisz zrobić, to zmienić

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

do

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

I jesteś gotowy.

kayleeFrye_onDeck
źródło
2
Potrzebowałem szybkiego rozwiązania i to działało świetnie! Dzięki za udostępnienie.
Mark Kram,
1
Brakowało rozszerzenia .Show w przykładach ... Powinien brzmieć: Wynik DialogResult = MessageBoxEx.Show ("Text", "Title", MessageBoxButtons.CHOICE, timer_ms)
Edd
2

TUTAJ dostępny jest projekt codeproject, który zapewnia taką funkcjonalność.

Podążanie za wieloma wątkami na SO i innych forach nie może być zrobione za pomocą zwykłego MessageBox.

Edytować:

Mam pomysł, który jest trochę ehmmm tak ...

Użyj licznika czasu i zacznij, gdy pojawi się MessageBox. Jeśli MessageBox nasłuchuje tylko przycisku OK (tylko 1 możliwość), użyj zdarzenia OnTick, aby emulować naciśnięcie klawisza ESC, SendKeys.Send("{ESC}");a następnie zatrzymaj licznik czasu.

jAC
źródło
1
Koncepcja timera jest prostym sposobem ... ale musisz upewnić się, że wysłane klucze trafią do twojej aplikacji, jeśli nie ma lub traci fokus. Wymagałoby to SetForegroundWindow, a odpowiedź zaczyna zawierać więcej kodu, ale zobacz poniżej „AppActivate”.
FastAl
2

Kod DMitryG „pobierz wartość zwracaną elementu bazowego MessageBox” zawiera błąd, więc timerResult nigdy nie jest właściwie zwracany ( MessageBox.Showwywołanie zwraca PO OnTimerElapsedzakończeniu). Moja poprawka jest poniżej:

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
źródło
1

Biblioteka Vb.net ma proste rozwiązanie wykorzystujące do tego klasę interakcji:

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
źródło
0

W user32.dll znajduje się nieudokumentowany interfejs API o nazwie MessageBoxTimeout (), ale wymaga systemu Windows XP lub nowszego.

Greg Wittmeyer
źródło
0

użyj EndDialogzamiast wysyłać WM_CLOSE:

[DllImport("user32.dll")]
public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);
Kaven Wu
źródło
0

Zrobiłem to w ten sposób

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
źródło