From 886245cc61d6cac59413ad82108811a48e46a68b Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Sun, 3 May 2026 10:46:53 +0200 Subject: [PATCH] =?UTF-8?q?v0.16.0=20=E2=80=94=20ThemedMessageBox=20replac?= =?UTF-8?q?ing=20native=20MessageBox=20everywhere?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- installer/PSLauncher.iss | 2 +- src/PSLauncher.App/PSLauncher.App.csproj | 6 +- .../ViewModels/MainViewModel.cs | 28 +-- .../ViewModels/SettingsViewModel.cs | 11 +- .../Views/LicenseDetailsDialog.xaml.cs | 2 +- src/PSLauncher.App/Views/MainWindow.xaml.cs | 2 +- .../Views/ThemedMessageBox.xaml | 72 +++++++ .../Views/ThemedMessageBox.xaml.cs | 187 ++++++++++++++++++ src/PSLauncher.Core/Localization/Strings.cs | 3 + .../PSLauncher.Updater.csproj | 2 +- 10 files changed, 289 insertions(+), 26 deletions(-) create mode 100644 src/PSLauncher.App/Views/ThemedMessageBox.xaml create mode 100644 src/PSLauncher.App/Views/ThemedMessageBox.xaml.cs diff --git a/installer/PSLauncher.iss b/installer/PSLauncher.iss index ed60e8c..819da29 100644 --- a/installer/PSLauncher.iss +++ b/installer/PSLauncher.iss @@ -11,7 +11,7 @@ #define MyAppName "PROSERVE Launcher" #define MyAppShortName "PS_Launcher" -#define MyAppVersion "0.15.0" +#define MyAppVersion "0.16.0" #define MyAppPublisher "ASTERION VR" #define MyAppURL "https://asterionvr.com" #define MyAppExeName "PS_Launcher.exe" diff --git a/src/PSLauncher.App/PSLauncher.App.csproj b/src/PSLauncher.App/PSLauncher.App.csproj index 2e63260..987f2e2 100644 --- a/src/PSLauncher.App/PSLauncher.App.csproj +++ b/src/PSLauncher.App/PSLauncher.App.csproj @@ -15,9 +15,9 @@ PROSERVE Launcher © 2026 ASTERION VR — All rights reserved PSLauncher.App - 0.15.0 - 0.15.0.0 - 0.15.0.0 + 0.16.0 + 0.16.0.0 + 0.16.0.0 true diff --git a/src/PSLauncher.App/ViewModels/MainViewModel.cs b/src/PSLauncher.App/ViewModels/MainViewModel.cs index 291654e..fdca5e0 100644 --- a/src/PSLauncher.App/ViewModels/MainViewModel.cs +++ b/src/PSLauncher.App/ViewModels/MainViewModel.cs @@ -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; } diff --git a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs index eec2f37..3a9bdc9 100644 --- a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs +++ b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs @@ -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); diff --git a/src/PSLauncher.App/Views/LicenseDetailsDialog.xaml.cs b/src/PSLauncher.App/Views/LicenseDetailsDialog.xaml.cs index ac3230a..294b0fd 100644 --- a/src/PSLauncher.App/Views/LicenseDetailsDialog.xaml.cs +++ b/src/PSLauncher.App/Views/LicenseDetailsDialog.xaml.cs @@ -66,7 +66,7 @@ public partial class LicenseDetailsDialog : Window private void OnDeactivate(object sender, RoutedEventArgs e) { - var confirm = MessageBox.Show( + var confirm = ThemedMessageBox.Show( Strings.MsgDeactivateLicenseDetailedConfirm, Strings.MsgBoxConfirm, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); diff --git a/src/PSLauncher.App/Views/MainWindow.xaml.cs b/src/PSLauncher.App/Views/MainWindow.xaml.cs index 5bf59c0..c3979ed 100644 --- a/src/PSLauncher.App/Views/MainWindow.xaml.cs +++ b/src/PSLauncher.App/Views/MainWindow.xaml.cs @@ -42,7 +42,7 @@ public partial class MainWindow : Window if (DataContext is MainViewModel vm && vm.HasActiveDownload) { var version = vm.ActiveDownloadVersion ?? "?"; - var result = MessageBox.Show( + var result = ThemedMessageBox.Show( Strings.MsgQuitWhileDownloadingConfirm(version), Strings.MsgBoxQuit, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); diff --git a/src/PSLauncher.App/Views/ThemedMessageBox.xaml b/src/PSLauncher.App/Views/ThemedMessageBox.xaml new file mode 100644 index 0000000..fec94ed --- /dev/null +++ b/src/PSLauncher.App/Views/ThemedMessageBox.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + +