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
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
źródło
MessageBox
Odpowiedzi:
Wypróbuj następujące podejście:
AutoClosingMessageBox.Show("Text", "Caption", 1000);
Gdzie
AutoClosingMessageBox
klasa 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
YesNo
przyciskami i odkryłem, że podejście z wysłaniemWM_CLOSE
wiadomoś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 :
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;
źródło
#32770
value jako nazwy klasySystem.Windows.Forms.MessageBoxButtons.YesNo
YesNo
przyciskami - 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!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 zTaskFactory.StartNew
lubTask.Run
z parametrami domyślnymi).źródło
Task
iTaskScheduler
pochodzą z przestrzeni nazwSystem.Threading.Tasks
w mscorlib.dll, więc nie są potrzebne żadne dodatkowe odwołania do zestawu.w.SentToBack()
tuż przedMessageBox.Show()
, okno dialogowe nadal było wyświetlane u góry głównego formularza. Przetestowano na platformach .NET 4.5 i 4.7.1.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!
źródło
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.
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); }
źródło
System.Windows
iSystem.Windows.Forms
zajęł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
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); }
źródło
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.
źródło
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.źródło
Kod DMitryG „pobierz wartość zwracaną elementu bazowego
MessageBox
” zawiera błąd, więc timerResult nigdy nie jest właściwie zwracany (MessageBox.Show
wywołanie zwraca POOnTimerElapsed
zakoń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); }
źródło
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); }
źródło
W user32.dll znajduje się nieudokumentowany interfejs API o nazwie MessageBoxTimeout (), ale wymaga systemu Windows XP lub nowszego.
źródło
użyj
EndDialog
zamiast wysyłaćWM_CLOSE
:[DllImport("user32.dll")] public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);
źródło
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);
źródło