Is there an easy way to display a messagebox in VB.NET with custom button captions? I came across What is an easy way to create a MessageBox with custom button text in Managed C++?, in the Stack Overflow archives, but it's for Managed C++.
MessageBox uses a plain window that can be messed with like any other window. This has been possible in Windows for a very long time, well over 20 years already. The techniques are getting obscure though, too many friendly class wrappers that hide the native winapi and don't expose everything you can do with it. So much so that programmers now automatically assume that this isn't possible, as you can tell from the upvoted answers. It is the kind of programming that Petzold taught us in his seminal "Programming Windows" book. Replacing MessageBox with a custom Form or Window is actually fairly hard to do, it does non-trivial automatic layout to fit the text and supports localization without help. Although that's exactly what you don't seem to like :)
Anyhoo, the message box window is easy to find back. It is owned by the UI thread and has a special class name that makes it unique. EnumThreadWindows() enumerates the windows owned by a thread, GetClassName() lets you check the kind of window. Then just poke the text into the button with SetWindowText().
Add a new class to your project and paste the code shown below. Invoke it with code like this:
Nobugz.PatchMsgBox(New String() {"Da", "Njet"})
MsgBox("gack", MsgBoxStyle.YesNo)
Here's the code:
Imports System.Text
Imports System.Runtime.InteropServices
Public Class Nobugz
Private Shared mLabels() As String '' Desired new labels
Private Shared mLabelIndex As Integer '' Next caption to update
Public Shared Sub PatchMsgBox(ByVal labels() As String)
''--- Updates message box buttons
mLabels = labels
Application.OpenForms(0).BeginInvoke(New FindWindowDelegate(AddressOf FindMsgBox), GetCurrentThreadId())
End Sub
Private Shared Sub FindMsgBox(ByVal tid As Integer)
''--- Enumerate the windows owned by the UI thread
EnumThreadWindows(tid, AddressOf EnumWindow, IntPtr.Zero)
End Sub
Private Shared Function EnumWindow(ByVal hWnd As IntPtr, ByVal lp As IntPtr) As Boolean
''--- Is this the message box?
Dim sb As New StringBuilder(256)
GetClassName(hWnd, sb, sb.Capacity)
If sb.ToString() <> "#32770" Then Return True
''--- Got it, now find the buttons
mLabelIndex = 0
EnumChildWindows(hWnd, AddressOf FindButtons, IntPtr.Zero)
Return False
End Function
Private Shared Function FindButtons(ByVal hWnd As IntPtr, ByVal lp As IntPtr) As Boolean
Dim sb As New StringBuilder(256)
GetClassName(hWnd, sb, sb.Capacity)
If sb.ToString() = "Button" And mLabelIndex <= UBound(mLabels) Then
''--- Got one, update text
SetWindowText(hWnd, mLabels(mLabelIndex))
mLabelIndex += 1
End If
Return True
End Function
''--- P/Invoke declarations
Private Delegate Sub FindWindowDelegate(ByVal tid As Integer)
Private Delegate Function EnumWindowDelegate(ByVal hWnd As IntPtr, ByVal lp As IntPtr) As Boolean
Private Declare Auto Function EnumThreadWindows Lib "user32.dll" (ByVal tid As Integer, ByVal callback As EnumWindowDelegate, ByVal lp As IntPtr) As Boolean
Private Declare Auto Function EnumChildWindows Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal callback As EnumWindowDelegate, ByVal lp As IntPtr) As Boolean
Private Declare Auto Function GetClassName Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal name As StringBuilder, ByVal maxlen As Integer) As Integer
Private Declare Auto Function GetCurrentThreadId Lib "kernel32.dll" () As Integer
Private Declare Auto Function SetWindowText Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal text As String) As Boolean
End Class