v0.16.0 — ThemedMessageBox replacing native MessageBox everywhere

The Win32 MessageBox uses the system white-on-gray look that clashes
with the launcher's dark theme (jarring transition every time we
confirm a cancel, show an error, etc.). Replaced with a custom WPF
dialog that:

- Drops in : same Show(message, title, button, icon, default) API as
  System.Windows.MessageBox, returns the same MessageBoxResult.
- Looks native to the launcher : dark Brush.Bg.Window background,
  Brush.Border outline, Brush.Text.Primary content, AccentButton for
  the default action and SecondaryButton for the others.
- Iconography by emoji + colour code :  red (Error), ⚠ amber (Warning),
   neutral (Question), ℹ blue (Information). Mapped from MessageBoxImage.
- Buttons localised via Strings.ActionOk / ActionYes / ActionNo /
  ActionCancel — works in fr/en/zh/th/ar like the rest of the UI.
- Owner = currently active window (falls back to MainWindow then
  CenterScreen) so it positions correctly even from a child dialog.
- Esc resolves to Cancel/No matching MessageBox semantics.

The 21 MessageBox.Show call sites across MainViewModel, SettingsViewModel,
LicenseDetailsDialog and MainWindow now use ThemedMessageBox.Show with
no signature change — full grep replace + tiny `using` adjustments.

Versions bumped to 0.16.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 10:46:53 +02:00
parent 42dd0701f5
commit 886245cc61
10 changed files with 289 additions and 26 deletions

View File

@@ -438,7 +438,7 @@ public sealed partial class MainViewModel : ObservableObject
catch (Exception ex)
{
_logger.LogError(ex, "Self-update failed");
MessageBox.Show(Strings.MsgSelfUpdateFailed(ex.Message),
ThemedMessageBox.Show(Strings.MsgSelfUpdateFailed(ex.Message),
Strings.MsgBoxError, MessageBoxButton.OK, MessageBoxImage.Error);
StatusMessage = Strings.StatusSelfUpdateError(ex.Message);
IsBusy = false;
@@ -518,7 +518,7 @@ public sealed partial class MainViewModel : ObservableObject
private void CancelDownload()
{
if (_activeDownloadCts is null || _activeRow is null) return;
var confirm = MessageBox.Show(
var confirm = ThemedMessageBox.Show(
Strings.MsgCancelDownloadConfirm(_activeRow.Version),
Strings.MsgBoxCancelDl,
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
@@ -535,7 +535,7 @@ public sealed partial class MainViewModel : ObservableObject
// Si un DL est actif sur cette même row, on doit d'abord l'annuler proprement.
var st = _downloadManager.GetResumableState(row.Version);
var sizeStr = FormatSize(st?.DownloadedBytes ?? 0);
var confirm = MessageBox.Show(
var confirm = ThemedMessageBox.Show(
Strings.MsgRestartDownloadConfirm(row.Version, sizeStr),
Strings.MsgBoxRestartDl,
MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
@@ -605,7 +605,7 @@ public sealed partial class MainViewModel : ObservableObject
catch (Exception ex)
{
_logger.LogError(ex, "Launch failed");
MessageBox.Show(Strings.MsgLaunchFailed(ex.Message),
ThemedMessageBox.Show(Strings.MsgLaunchFailed(ex.Message),
Strings.MsgBoxLaunchError, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -613,7 +613,7 @@ public sealed partial class MainViewModel : ObservableObject
private async Task InstallVersionAsync(VersionRowViewModel row)
{
if (row.Remote is null) return;
if (IsBusy) { MessageBox.Show(Strings.MsgBusy, Strings.MsgBoxPatience, MessageBoxButton.OK, MessageBoxImage.Information); return; }
if (IsBusy) { ThemedMessageBox.Show(Strings.MsgBusy, Strings.MsgBoxPatience, MessageBoxButton.OK, MessageBoxImage.Information); return; }
IsBusy = true;
_activeRow = row;
@@ -738,7 +738,7 @@ public sealed partial class MainViewModel : ObservableObject
// notifie l'utilisateur que la DB n'a pas été migrée — la prochaine relance
// de l'install (ou un Settings → "Rejouer migrations") tentera à nouveau.
_logger.LogError("DB migration failed for v{Version}: {Detail}", row.Version, migResult.FailureMessage);
MessageBox.Show(
ThemedMessageBox.Show(
Strings.MsgMigrationFailed(migResult.FailureMessage ?? "?"),
Strings.MsgBoxMigrationFailed,
MessageBoxButton.OK, MessageBoxImage.Warning);
@@ -779,13 +779,13 @@ public sealed partial class MainViewModel : ObservableObject
_logger.LogInformation("No _report/ in v{Version} ZIP, report tool unchanged", row.Version);
break;
case DeployStatus.XamppNotFound:
MessageBox.Show(
ThemedMessageBox.Show(
Strings.MsgReportXamppNotFound(_config.ReportTool.HtdocsRoot),
Strings.MsgBoxReportDeployFailed,
MessageBoxButton.OK, MessageBoxImage.Warning);
break;
case DeployStatus.Failed:
MessageBox.Show(
ThemedMessageBox.Show(
Strings.MsgReportDeployFailed(rdResult.ErrorMessage ?? "?"),
Strings.MsgBoxReportDeployFailed,
MessageBoxButton.OK, MessageBoxImage.Warning);
@@ -818,7 +818,7 @@ public sealed partial class MainViewModel : ObservableObject
row.State = VersionRowState.AvailableIdle;
row.ProgressDetail = null;
row.ProgressPercent = 0;
MessageBox.Show(
ThemedMessageBox.Show(
Strings.MsgInstallFailed(ex.Message),
Strings.MsgBoxError, MessageBoxButton.OK, MessageBoxImage.Error);
StatusMessage = Strings.StatusError(ex.Message);
@@ -841,14 +841,14 @@ public sealed partial class MainViewModel : ObservableObject
if (row.Installed is null) return;
var sizeStr = FormatSize(row.SizeBytes);
var confirm = MessageBox.Show(
var confirm = ThemedMessageBox.Show(
Strings.MsgUninstallConfirm(row.Version, row.FolderPath, sizeStr),
Strings.MsgBoxConfirm,
MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
if (confirm != MessageBoxResult.Yes) return;
if (IsBusy) { MessageBox.Show(Strings.MsgBusy, Strings.MsgBoxPatience, MessageBoxButton.OK, MessageBoxImage.Information); return; }
if (IsBusy) { ThemedMessageBox.Show(Strings.MsgBusy, Strings.MsgBoxPatience, MessageBoxButton.OK, MessageBoxImage.Information); return; }
IsBusy = true;
row.State = VersionRowState.Uninstalling;
@@ -863,7 +863,7 @@ public sealed partial class MainViewModel : ObservableObject
{
_logger.LogError(ex, "Uninstall failed for {Version}", row.Version);
row.State = VersionRowState.InstalledIdle;
MessageBox.Show(Strings.MsgUninstallFailed(ex.Message), Strings.MsgBoxError,
ThemedMessageBox.Show(Strings.MsgUninstallFailed(ex.Message), Strings.MsgBoxError,
MessageBoxButton.OK, MessageBoxImage.Error);
StatusMessage = Strings.StatusError(ex.Message);
}
@@ -878,7 +878,7 @@ public sealed partial class MainViewModel : ObservableObject
var url = row.Remote?.ReleaseNotesUrl;
if (string.IsNullOrEmpty(url))
{
MessageBox.Show(Strings.MsgNoReleaseNotes,
ThemedMessageBox.Show(Strings.MsgNoReleaseNotes,
Strings.MsgBoxReleaseNotes, MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
@@ -887,7 +887,7 @@ public sealed partial class MainViewModel : ObservableObject
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch release notes");
MessageBox.Show(Strings.MsgReleaseNotesFetchFailed(ex.Message),
ThemedMessageBox.Show(Strings.MsgReleaseNotesFetchFailed(ex.Message),
Strings.MsgBoxError, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}

View File

@@ -6,6 +6,7 @@ using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using PSLauncher.App.Views;
using PSLauncher.Core.Configuration;
using PSLauncher.Core.Downloads;
using PSLauncher.Core.Installations;
@@ -239,7 +240,7 @@ public sealed partial class SettingsViewModel : ObservableObject
[RelayCommand]
private void ClearCache()
{
var confirm = MessageBox.Show(
var confirm = ThemedMessageBox.Show(
Strings.MsgClearCacheConfirm,
Strings.MsgBoxConfirm,
MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
@@ -256,7 +257,7 @@ public sealed partial class SettingsViewModel : ObservableObject
catch (Exception ex)
{
_logger.LogError(ex, "ClearCache failed");
MessageBox.Show(ex.Message, Strings.MsgBoxError, MessageBoxButton.OK, MessageBoxImage.Error);
ThemedMessageBox.Show(ex.Message, Strings.MsgBoxError, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -304,7 +305,7 @@ public sealed partial class SettingsViewModel : ObservableObject
// Le launcher doit être relancé pour que les bindings x:Static reflètent
// la nouvelle langue. Plutôt que de demander à l'utilisateur de fermer/rouvrir,
// on spawne un cmd qui attend la fermeture puis relance.
var ok = MessageBox.Show(
var ok = ThemedMessageBox.Show(
Strings.MsgLanguageRestart,
Strings.MsgBoxLanguageChange,
MessageBoxButton.OKCancel, MessageBoxImage.Information, MessageBoxResult.OK);
@@ -335,7 +336,7 @@ public sealed partial class SettingsViewModel : ObservableObject
[RelayCommand]
private void DeactivateLicense()
{
var confirm = MessageBox.Show(
var confirm = ThemedMessageBox.Show(
Strings.MsgDeactivateLicenseConfirm,
Strings.MsgBoxConfirm,
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
@@ -526,7 +527,7 @@ public sealed partial class SettingsViewModel : ObservableObject
{
var localDate = backup.TimestampUtc.ToLocalTime();
var dateLabel = Strings.FormatLongDate(localDate);
var confirm = MessageBox.Show(
var confirm = ThemedMessageBox.Show(
Strings.MsgRevertReportConfirm(dateLabel),
Strings.MsgBoxRevertReport,
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);