Đóng Hộp thư sau vài giây


82

Tôi có ứng dụng Windows Forms VS2010 C # nơi tôi hiển thị Hộp thư để hiển thị thông báo.

Tôi có một nút không sao, nhưng nếu họ bỏ đi, tôi muốn hết thời gian chờ và đóng hộp tin nhắn sau khi cho phép 5 giây, tự động đóng hộp tin nhắn.

Có MessageBox tùy chỉnh (kế thừa từ Biểu mẫu) hoặc Biểu mẫu báo cáo khác, nhưng sẽ rất thú vị nếu không cần Biểu mẫu.

Bất kỳ đề xuất hoặc mẫu về nó?

Đã cập nhật:

Đối với WPF
Tự động đóng hộp thư trong C #

Hộp thư tùy chỉnh (sử dụng kế thừa Biểu mẫu)
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/

Hộp thư có thể cuộn được Hộp thư có thể
cuộn được trong C #

Trình báo cáo ngoại lệ
/programming/49224/good-crash-reporting-library-in-c-sharp

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

Giải pháp:

Có lẽ tôi nghĩ các câu trả lời sau đây là giải pháp tốt mà không cần sử dụng Biểu mẫu.

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


1
Hãy xem điều này (Windows Phone, nhưng phải giống nhau): stackoverflow.com/questions/9674122/…
jAC

6
@istepaniuk anh ấy không thể thử nếu anh ấy không biết. nên dừng lại mà loại câu hỏi
Mustafa Ekici

1
Bạn sẽ có thể tạo ra một bộ đếm thời gian và thiết lập nó để đóng sau một khoảng thời gian
Steven ackley

2
Bạn có thể tạo Form như mộtMessageBox
spajce

2
@MustafaEkici, tôi đã mời OP trình bày những gì anh ấy đã thử. Tôi cho rằng anh ấy phải đã thử và thất bại trước khi thực sự hỏi bằng SO. Đó là lý do tại sao tôi và Ramhound từ chối câu hỏi. Bạn có thể đọc meta.stackexchange.com/questions/122986/…
istepaniuk

Câu trả lời:


123

Hãy thử cách tiếp cận sau:

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

Trường hợp AutoClosingMessageBoxlớp thực hiện như sau:

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

Cập nhật: Nếu bạn muốn nhận giá trị trả về của MessageBox bên dưới khi người dùng chọn thứ gì đó trước thời gian chờ, bạn có thể sử dụng phiên bản sau của mã này:

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

Tuy nhiên, một bản cập nhật khác

Tôi đã kiểm tra trường hợp của @ Jack với các YesNonút và phát hiện ra rằng phương pháp gửi WM_CLOSEtin nhắn không hoạt động chút nào.
Tôi sẽ cung cấp một bản sửa lỗi trong ngữ cảnh của thư viện AutoclosingMessageBox riêng biệt . Thư viện này chứa cách tiếp cận được thiết kế lại và tôi tin rằng có thể hữu ích cho ai đó.
Nó cũng có sẵn thông qua gói NuGet :

Install-Package AutoClosingMessageBox

Ghi chú phát hành (v1.0.0.2):
- API Chương trình mới (IWin32Owner) để hỗ trợ hầu hết các tình huống phổ biến (trong bối cảnh # 1 );
- API Nhà máy () mới để cung cấp toàn quyền kiểm soát trên MessageBox hiển thị;


Sử dụng tốt hơn System.Threading.Timer hoặc System.Timers.Timer (như câu trả lời @Jens)? SendMessage so với PostMessage?
Kiquenet

@Kiquenet Tôi tin rằng không có sự khác biệt đáng kể nào trong tình huống cụ thể này.
DmitryG

1
@GeorgeBirbilis Cảm ơn, nó có thể làm cho một cảm giác ... Trong trường hợp này, bạn có thể sử dụng #32770giá trị như một tên lớp
DmitryG

2
Nó không hoạt động đối với tôi nếu các nútSystem.Windows.Forms.MessageBoxButtons.YesNo
Jack

1
@Jack Xin lỗi vì trả lời muộn, tôi đã kiểm tra trường hợp có các YesNonút - bạn hoàn toàn chính xác - nó không hoạt động. Tôi sẽ cung cấp một bản sửa lỗi trong ngữ cảnh thư viện AutoclosingMessageBox riêng biệt . Nội dung này chứa cách tiếp cận được thiết kế lại và tôi tin rằng có thể hữu ích. Cảm ơn!
DmitryG

32

Một giải pháp hoạt động trong 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);

Dựa trên hiệu ứng đóng biểu mẫu sở hữu hộp thư cũng sẽ đóng hộp.

Các điều khiển Windows Forms có yêu cầu rằng chúng phải được truy cập trên cùng một chuỗi đã tạo ra chúng. Việc sử dụng TaskScheduler.FromCurrentSynchronizationContext()sẽ đảm bảo rằng, giả sử rằng mã ví dụ ở trên được thực thi trên chuỗi giao diện người dùng hoặc một chuỗi do người dùng tạo. Ví dụ sẽ không hoạt động chính xác nếu mã được thực thi trên một luồng từ nhóm luồng (ví dụ: gọi lại bộ hẹn giờ) hoặc nhóm tác vụ (ví dụ: trên một tác vụ được tạo bằng TaskFactory.StartNewhoặc Task.Runvới các tham số mặc định).


Phiên bản .NET là gì? Nó là gì TaskScheduler?
Kiquenet,

1
@Kiquenet .NET 4.0 trở lên. TaskTaskSchedulertừ không gian tên System.Threading.Taskstrong mscorlib.dll nên không cần tham chiếu lắp ráp bổ sung.
BSharp

Giải pháp tuyệt vời! Một bổ sung ... điều này hoạt động tốt trong ứng dụng Winforms, SAU KHI thêm BringToFront cho biểu mẫu mới, ngay sau khi tạo nó. Nếu không, các hộp thoại đôi khi được hiển thị phía sau biểu mẫu hoạt động hiện tại, tức là không xuất hiện cho người dùng. var w = new Form () {Size = new Size (0, 0)}; w.BringToFront ();
Nhà phát triển

@ Developer63 Tôi không thể tái tạo trải nghiệm của bạn. Ngay cả khi gọi w.SentToBack()ngay trước đó MessageBox.Show(), hộp thoại vẫn hiển thị ở phía trên của biểu mẫu chính. Đã thử nghiệm trên .NET 4.5 và 4.7.1.
BSharp

Giải pháp này có thể là tốt đẹp, nhưng nó chỉ có sẵn từ .NET 4.5 trở lên, không phải là 4.0 (vì Task.Delay) xem: stackoverflow.com/questions/17717047/...
KwentRell

16

Kích hoạt ứng dụng!

Nếu bạn không ngại làm xáo trộn các tham chiếu của mình một chút, bạn có thể bao gồm Microsoft.Visualbasic,và sử dụng cách rất ngắn này.

Hiển thị MessageBox

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

Chức năng CloseIt:

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

Bây giờ đi rửa tay của bạn!


11

Phương thức System.Windows.MessageBox.Show () có quá tải lấy một cửa sổ chủ sở hữu làm tham số đầu tiên. Nếu chúng tôi tạo một Cửa sổ chủ sở hữu vô hình mà sau đó chúng tôi đóng sau một thời gian nhất định, hộp thông báo con của nó cũng sẽ đóng lại.

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

Càng xa càng tốt. Nhưng làm thế nào để chúng ta đóng cửa sổ nếu chuỗi giao diện người dùng bị chặn bởi hộp thông báo và không thể truy cập các điều khiển giao diện người dùng từ chuỗi công nhân? Câu trả lời là - bằng cách gửi thông báo cửa sổ WM_CLOSE tới tay cầm cửa sổ chủ sở hữu:

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

Và đây là quá trình nhập cho phương thức 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);
}

Loại cửa sổ dành cho Windows Forms?
Kiquenet

Tại sao bạn cần gửi tin nhắn đến cửa sổ cha ẩn để đóng nó? Bạn không thể chỉ gọi một số phương thức "Đóng" trên nó hoặc loại bỏ nó theo cách khác?
George Birbilis

Để trả lời câu hỏi của riêng tôi, thuộc tính OwnedWindows của Cửa sổ WPF đó dường như hiển thị 0 cửa sổ và Đóng không đóng hộp thư con
George Birbilis

2
Giải pháp tuyệt vời. Có một số trùng lặp về tên System.WindowsSystem.Windows.Formsđiều đó khiến tôi mất một thời gian để tìm ra. Bạn sẽ cần những điều sau đây: System, System.Runtime.InteropServices, System.Threading.Tasks, System.Windows, System.Windows.Interop,System.Windows.Media
m3tikn0b

10

Bạn có thể thử điều này:

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

2
Sử dụng tốt hơn System.Threading.Timer hoặc System.Timers.Timer (như câu trả lời @DmitryG)? SendMessage so với PostMessage?
Kiquenet

1
xem stackoverflow.com/questions/3376619/… và có thể cũng stackoverflow.com/questions/2411116/… về sự khác biệt giữa SendMessage và PostMessage
George Birbilis

7

RogerB tại CodeProject có một trong những giải pháp khéo léo nhất cho câu trả lời này, và anh ấy đã làm điều đó vào năm '04, và nó vẫn còn bangin '

Về cơ bản, bạn vào đây dự án của anh ấy và tải xuống tệp CS . Trong trường hợp liên kết đó bị chết, tôi có một ý chính dự phòng ở đây. Thêm tệp CS vào dự án của bạn hoặc sao chép / dán mã vào đâu đó nếu bạn muốn làm điều đó.

Sau đó, tất cả những gì bạn phải làm là chuyển đổi

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

đến

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

Và bạn tốt để đi.


2
Tôi cần một giải pháp nhanh chóng và điều này đã hoạt động tốt! Cám ơn vì đã chia sẻ.
Mark Kram

1
Bạn đã bỏ lỡ phần mở rộng .Show trong các ví dụ của mình ... Nên đọc: DialogResult result = MessageBoxEx.Show ("Văn bản", "Tiêu đề", MessageBoxButtons.CHOICE, timer_ms)
Edd

2

Có một dự án codeproject có sẵn TẠI ĐÂY cung cấp tính năng này.

Theo nhiều chủ đề ở đây trên SO và các bảng khác, điều này không thể được thực hiện với MessageBox bình thường.

Biên tập:

Tôi có một ý tưởng hơi ehmmm vâng ..

Sử dụng bộ hẹn giờ và bắt đầu khi MessageBox xuất hiện. Nếu MessageBox của bạn chỉ nghe nút OK (chỉ có 1 khả năng) thì hãy sử dụng OnTick-Event để mô phỏng một ESC-Press với SendKeys.Send("{ESC}");và sau đó dừng bộ đếm thời gian.


1
Khái niệm bộ hẹn giờ là một cách đơn giản ... nhưng phải đảm bảo các phím đã gửi chạm vào ứng dụng của bạn nếu nó không có hoặc mất tiêu điểm. Điều đó sẽ yêu cầu SetForegroundWindow và câu trả lời bắt đầu bao gồm nhiều mã hơn, nhưng hãy xem 'AppActivate' bên dưới.
FastAl

2

Mã của DMitryG "lấy giá trị trả về của phần bên dưới MessageBox" có lỗi nên timerResult không bao giờ thực sự được trả về chính xác ( MessageBox.Showcuộc gọi trả về SAU KHI OnTimerElapsedhoàn thành). Bản sửa lỗi của tôi ở bên dưới:

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

1

Thư viện Vb.net có một giải pháp đơn giản bằng cách sử dụng lớp tương tác cho việc này:

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

0

Có một API không có tài liệu trong user32.dll có tên MessageBoxTimeout () nhưng nó yêu cầu Windows XP trở lên.


0

sử dụng EndDialogthay vì gửi WM_CLOSE:

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

0

Tôi đã làm nó như thế này

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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.