Notes

MessageBoxEx does not support reentrancy. Good thing too. Why would you want to have more than one message box at the same time?

If the user doesn't press a button before the timeout elapses the function will return the
DialogResult of the messagebox dialogs default button.

How does it work?

Just before calling MessageBox.Show(...), SetWindowsHookEx(WH_CALLWNDPROCRET, ...) is called. The hook proc looks for a
WM_INITDIALOG on a window with text equal to the message box caption. A windows timer is started on that window with the appropriate timeout. When the timeout fires
EndDialog is called with the result set to the dialog default button ID. You can get that ID by sending a dialog box a
DM_GETDEFID message. Pretty simple.

References

KB318804: HOW TO: Set a Windows Hook in Visual C#.NET

Acknowledgements

GipsySoft: http://www.gipsysoft.com/messagebox. You'll find a C++ implementation with many more features.

Warning 5 'System.AppDomain.GetCurrentThreadId()' is obsolete: 'AppDomain.GetCurrentThreadId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread. http://go.microsoft.com/fwlink/?linkid=14202'

First let me thank you for this quick and smart implementation. My 5 for the article. Secondly, as a part of using the code in my project which is in C++/CLI, I converted the entire code into C++/CLI including some changes required to suit the C++ language rules and to handle deprecations. I would like to give the code back to the community. Can you please upload it as an addendum to this article? Please let me know how to send you the code too. Thanks!

1. The remaining time will be shown either in text or in caption if the placeholder {0} can be found.
2. The timeout can be set to 0 to have no timing.
3. Possibility to show a checkbox below the buttons - e.g.: Dont ask me again. The checkstate will be
returned as CheckAndExDialogResult
4. New DialogResults (ExDialogResult): Save, Discard, TryAgain and Continue to use this MessageBox also as
"Unsaved changes" dialog with save, discard and cancel results; therefore is does not longer return the
System.Windows.Forms.DialogResult.
5. Adding of language support: The button text will be replaced by GUI language - if there are native
speakers of below languages pls. feel free to correct the google translations. Thanks for that!!
Additional languages can be easy included: Just a the related enumeration and change the small part
within the sub: _setLanguage.
The languages are: English, German, French, Spain, Portugese, Polish, Czech and Dutch.

If you use some of the methods suggested in these comments to replace AppDomain.GetCurrentThreadId() with a method from System.Threading.Thread.... to fix the deprecation warning, this will NOT work in windows 7. So, instead, can I suggest the method used by Dana Hall in teh comments below.

Timers belong to an HWND. An HWND belongs to the thread it was created on. The WM_TIMER will be received on the thread the HWND belongs to. So what does it matter what the timer ID is? As long as no one else is using it for a given HWND you're good to go. And since this is a MessageBox its pretty safe no one else is using ID 42.