Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion v2/internal/frontend/desktop/windows/dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"syscall"

"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
"golang.org/x/sys/windows"
Expand Down Expand Up @@ -177,6 +178,15 @@ func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
return flags
}

// messageDialogUserIconFlags prepares flags for MB_USERICON: the stock icon bits
// must be stripped (mixing MB_ICON* with MB_USERICON is undefined), while the
// button configuration is preserved (#4233). MB_SYSTEMMODAL is kept: it controls
// modality, not the icon.
func messageDialogUserIconFlags(flags uint32) uint32 {
const mbIconMask = windows.MB_ICONHAND | windows.MB_ICONQUESTION | windows.MB_ICONASTERISK | windows.MB_USERICON
return (flags &^ mbIconMask) | windows.MB_USERICON | windows.MB_SYSTEMMODAL
}

// MessageDialog show a message dialog to the user
func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {

Expand All @@ -191,7 +201,16 @@ func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string,

flags := calculateMessageDialogFlags(options)

button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL)
// Show the application icon (the same icon the main window uses) in the
// message box; MessageBoxW can only show the four stock icons.
hwnd := f.getHandleForDialog()
button, err := w32.MessageBoxWithIcon(hwnd, message, title, uint16(winc.AppIconID), messageDialogUserIconFlags(flags))
if err != nil {
// The icon resource is not embedded in dev builds (`go run`, `wails dev`),
// which makes MessageBoxIndirect fail outright (#4233). Fall back to a
// stock-icon dialog so the message is still shown.
button, _ = windows.MessageBox(windows.HWND(hwnd), message, title, flags|windows.MB_SYSTEMMODAL)
}
// This maps MessageBox return values to strings
responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
result := "Error"
Expand Down
61 changes: 61 additions & 0 deletions v2/internal/frontend/desktop/windows/dialog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,64 @@ func Test_calculateMessageDialogFlags(t *testing.T) {
})
}
}

func Test_messageDialogUserIconFlags(t *testing.T) {
tests := []struct {
name string
options frontend.MessageDialogOptions
want uint32
}{
{
name: "Test Info Dialog",
options: frontend.MessageDialogOptions{
Type: frontend.InfoDialog,
},
want: windows.MB_OK | windows.MB_USERICON | windows.MB_SYSTEMMODAL,
},
{
name: "Test Error Dialog",
options: frontend.MessageDialogOptions{
Type: frontend.ErrorDialog,
},
want: windows.MB_OK | windows.MB_USERICON | windows.MB_SYSTEMMODAL,
},
{
name: "Test Question Dialog",
options: frontend.MessageDialogOptions{
Type: frontend.QuestionDialog,
},
want: windows.MB_YESNO | windows.MB_USERICON | windows.MB_SYSTEMMODAL,
},
{
name: "Test Question Dialog with default cancel",
options: frontend.MessageDialogOptions{
Type: frontend.QuestionDialog,
DefaultButton: "No",
},
want: windows.MB_YESNO | windows.MB_DEFBUTTON2 | windows.MB_USERICON | windows.MB_SYSTEMMODAL,
},
{
name: "Test Question Dialog with default cancel (lowercase)",
options: frontend.MessageDialogOptions{
Type: frontend.QuestionDialog,
DefaultButton: "no",
},
want: windows.MB_YESNO | windows.MB_DEFBUTTON2 | windows.MB_USERICON | windows.MB_SYSTEMMODAL,
},
{
name: "Test Warning Dialog",
options: frontend.MessageDialogOptions{
Type: frontend.WarningDialog,
},
want: windows.MB_OK | windows.MB_USERICON | windows.MB_SYSTEMMODAL,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flags := calculateMessageDialogFlags(tt.options)
if got := messageDialogUserIconFlags(flags); got != tt.want {
t.Errorf("messageDialogUserIconFlags() = %v, want %v", got, tt.want)
}
})
}
}
14 changes: 14 additions & 0 deletions v2/internal/frontend/desktop/windows/winc/w32/typedef.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ type MINMAXINFO struct {
PtMaxTrackSize POINT
}

// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msgboxparamsw
type MSGBOXPARAMS struct {
Size uint32
Owner HWND
Instance HINSTANCE
Text *uint16
Caption *uint16
Style uint32
Icon *uint16
ContextHelpID uintptr
MsgBoxCallback uintptr
LanguageID uint32
}

// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145037.aspx
type LOGFONT struct {
Height int32
Expand Down
24 changes: 24 additions & 0 deletions v2/internal/frontend/desktop/windows/winc/w32/user32.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ var (
procReleaseCapture = moduser32.NewProc("ReleaseCapture")
procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId")
procMessageBox = moduser32.NewProc("MessageBoxW")
procMessageBoxIndirect = moduser32.NewProc("MessageBoxIndirectW")
procGetSystemMetrics = moduser32.NewProc("GetSystemMetrics")
procPostThreadMessageW = moduser32.NewProc("PostThreadMessageW")
procRegisterWindowMessageA = moduser32.NewProc("RegisterWindowMessageA")
Expand Down Expand Up @@ -623,6 +624,29 @@ func MessageBox(hwnd HWND, title, caption string, flags uint) int {
return int(ret)
}

// MessageBoxWithIcon shows a modal message box that displays the icon stored in
// resource iconID of the given module. MessageBoxW cannot show an application
// icon, only the four stock icons. Returns an error if the icon resource is
// missing, which happens in dev builds (`go run`, `wails dev`) where no icon
// resource is embedded - see #4233.
func MessageBoxWithIcon(hwnd HWND, text, caption *uint16, iconID uint16, flags uint32) (int32, error) {
params := MSGBOXPARAMS{
Size: uint32(unsafe.Sizeof(MSGBOXPARAMS{})),
Owner: hwnd,
Instance: GetModuleHandle(""),
Text: text,
Caption: caption,
Style: flags,
Icon: (*uint16)(unsafe.Pointer(uintptr(iconID))), // MAKEINTRESOURCE
}

ret, _, err := procMessageBoxIndirect.Call(uintptr(unsafe.Pointer(&params)))
if ret == 0 {
return 0, err
}
return int32(ret), nil
}

func GetSystemMetrics(index int) int {
ret, _, _ := procGetSystemMetrics.Call(
uintptr(index))
Expand Down
4 changes: 4 additions & 0 deletions website/src/pages/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Fixed `MessageDialog` not showing the application icon on Windows [#3684](https://github.com/wailsapp/wails/issues/3684) by @DangTAnh

## v2.16.0 - 2026-09-14

### Fixed
Expand Down
Loading