manage window close, app menu quit, tray menu quit, with prevent shutdown and prevent shutdown dialog.
MODIFY
WalletWasabi.Fluent/App.axaml
WalletWasabi.Fluent/App.axaml
xmlns:local="using:WalletWasabi.Fluent"
xmlns:models="using:WalletWasabi.Models"
xmlns:vm="using:WalletWasabi.Fluent.ViewModels"
+ xmlns:converters="clr-namespace:WalletWasabi.Fluent.Converters"
x:DataType="vm:ApplicationViewModel"
x:CompileBindings="True"
x:Class="WalletWasabi.Fluent.App" CanShutdownProvider="{Binding .}">
<Application.Resources>
<x:Double x:Key="ToggleSwitchThemeMinWidth">0</x:Double>
+ <converters:BoolStringConverter x:Key="ShowHideBoolConverter" True="Hide" False="Show" />
</Application.Resources>
<Application.Styles>
</NativeMenu.Menu>
<TrayIcon.Icons>
<TrayIcons>
- <TrayIcon Icon="{Binding TrayIcon}" Clicked="TrayIcon_OnClicked" ToolTipText="Wasabi Wallet">
+ <TrayIcon Icon="{Binding TrayIcon}" Command="{Binding ShowCommand}" ToolTipText="Wasabi Wallet">
<NativeMenu.Menu>
<NativeMenu>
- <NativeMenuItem Header="Show" Command="{Binding ShowCommand} " />
+ <NativeMenuItem Header="{Binding IsMainWindowShown, Converter={StaticResource ShowHideBoolConverter}}" Command="{Binding ShowCommand} " />
<NativeMenuItemSeparator />
<NativeMenuItem Header="Quit" Command="{Binding QuitCommand}" />
</NativeMenu>

MODIFY
WalletWasabi.Fluent/App.axaml.cs
WalletWasabi.Fluent/App.axaml.cs
using System.Reactive.Concurrency;
+using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@@ -13,7 +14,9 @@ using WalletWasabi.Logging;
namespace WalletWasabi.Fluent;
-public class App : Application
+
+
+public class App : Application, IMainWindowService
{
private readonly Func<Task>? _backendInitialiseAsync;
@@ -29,9 +32,7 @@ public class App : Application
if (!Design.IsDesignMode)
{
- ApplicationViewModel applicationViewModel = new();
- DataContext = applicationViewModel;
- applicationViewModel.ShowRequested += (sender, args) => ShowRequested?.Invoke(sender, args);
+ DataContext = new ApplicationViewModel(this);
}
}
@@ -40,8 +41,6 @@ public class App : Application
_backendInitialiseAsync = backendInitialiseAsync;
}
- public event EventHandler? ShowRequested;
-
public ICanShutdownProvider? CanShutdownProvider
{
get => GetValue(CanShutdownProviderProperty);
@@ -59,12 +58,11 @@ public class App : Application
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
+ desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
+
desktop.ShutdownRequested += DesktopOnShutdownRequested;
- desktop.MainWindow = new MainWindow
- {
- DataContext = MainViewModel.Instance
- };
+ desktop.MainWindow = CreateMainWindow();
RxApp.MainThreadScheduler.Schedule(
async () =>
@@ -79,20 +77,62 @@ public class App : Application
base.OnFrameworkInitializationCompleted();
}
+ private Window CreateMainWindow()
+ {
+ var result = new MainWindow
+ {
+ DataContext = MainViewModel.Instance
+ };
+
+ Observable.FromEventPattern(result, nameof(result.Closed))
+ .Take(1)
+ .Subscribe(x =>
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is { })
+ {
+ desktop.MainWindow = null;
+
+ if (DataContext is ApplicationViewModel avm)
+ {
+ avm.IsMainWindowShown = false;
+ }
+ }
+ });
+
+ return result;
+ }
+
private void DesktopOnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
{
if (CanShutdownProvider is { } provider)
{
// Shutdown prevention will only work if you directly run the executable.
e.Cancel = !provider.CanShutdown();
+
+ if (e.Cancel && DataContext is ApplicationViewModel avm)
+ {
+ avm.OnClosePrevented();
+ }
+
Logger.LogDebug($"Cancellation of the shutdown set to: {e.Cancel}.");
}
}
- // Note, this is only supported on Win32 and some Linux DEs
- // https://github.com/AvaloniaUI/Avalonia/blob/99d983499f5412febf07aafe2bf03872319b412b/src/Avalonia.Controls/TrayIcon.cs#L66
- private void TrayIcon_OnClicked(object? sender, EventArgs e)
+ void IMainWindowService.Show()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is null)
+ {
+ desktop.MainWindow = CreateMainWindow();
+ desktop.MainWindow.Show();
+ }
+ }
+
+ void IMainWindowService.Close()
{
- ShowRequested?.Invoke(this, EventArgs.Empty);
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is { })
+ {
+ desktop.MainWindow.Close();
+ desktop.MainWindow = null;
+ }
}
}

ADD
WalletWasabi.Fluent/Providers/IMainWindowService.cs
+namespace WalletWasabi.Fluent.Providers;
+
+public interface IMainWindowService
+{
+ void Show();
+
+ void Close();
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/ApplicationViewModel.cs
WalletWasabi.Fluent/ViewModels/ApplicationViewModel.cs
+using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
+using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
@@ -13,33 +15,41 @@ using WalletWasabi.WabiSabi.Client;
namespace WalletWasabi.Fluent.ViewModels;
-public class ApplicationViewModel : ViewModelBase, ICanShutdownProvider
+public partial class ApplicationViewModel : ViewModelBase, ICanShutdownProvider
{
- public ApplicationViewModel()
- {
- QuitCommand = ReactiveCommand.CreateFromTask(async () =>
+ private readonly IMainWindowService _mainWindowService;
+ [AutoNotify] private bool _isMainWindowShown = true;
+
+ public ApplicationViewModel(IMainWindowService mainWindowService)
{
- if (CanShutdown())
+ _mainWindowService = mainWindowService;
+
+ this.WhenAnyValue(x => x.IsMainWindowShown)
+ .Where(x => x == false)
+ .Subscribe(async x =>
{
- if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime lifetime)
+ if (!Services.UiConfig.HideOnClose)
{
- lifetime.Shutdown();
+ DoQuit();
}
+ });
+
+ QuitCommand = ReactiveCommand.Create(DoQuit);
+
+ ShowCommand = ReactiveCommand.Create(() =>
+ {
+ if (IsMainWindowShown)
+ {
+ _mainWindowService.Close();
+ IsMainWindowShown = false;
}
else
{
- // Show the window if it was hidden.
- ShowRequested?.Invoke(this, EventArgs.Empty);
-
- await MainViewModel.Instance.CompactDialogScreen.NavigateDialogAsync(new Dialogs.ShowErrorDialogViewModel(
- "Wasabi is currently anonymising your wallet. Please try again in a few minutes.",
- "Warning",
- "Unable to close right now"));
+ _mainWindowService.Show();
+ IsMainWindowShown = true;
}
});
- ShowCommand = ReactiveCommand.Create(() => ShowRequested?.Invoke(this, EventArgs.Empty));
-
var dialogScreen = MainViewModel.Instance.DialogScreen;
AboutCommand = ReactiveCommand.Create(
@@ -69,10 +79,38 @@ public class ApplicationViewModel : ViewModelBase, ICanShutdownProvider
}
}
- public WindowIcon TrayIcon { get; }
+ private void DoQuit()
+ {
+ if (CanShutdown())
+ {
+ if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime lifetime)
+ {
+ lifetime.Shutdown();
+ }
+ }
+ else
+ {
+ _mainWindowService.Show();
+ IsMainWindowShown = true;
+
+ OnClosePrevented();
+ }
+ }
- public event EventHandler? ShowRequested;
+ public void OnClosePrevented()
+ {
+ _mainWindowService.Show();
+
+ RxApp.MainThreadScheduler.Schedule(async () =>
+ {
+ await MainViewModel.Instance.CompactDialogScreen.NavigateDialogAsync(new Dialogs.ShowErrorDialogViewModel(
+ "Wasabi is currently anonymising your wallet. Please try again in a few minutes.",
+ "Warning",
+ "Unable to close right now"));
+ });
+ }
+ public WindowIcon TrayIcon { get; }
public ICommand AboutCommand { get; }
public ICommand ShowCommand { get; }
public ICommand QuitCommand { get; }

MODIFY
WalletWasabi.Fluent/Views/MainWindow.axaml.cs
WalletWasabi.Fluent/Views/MainWindow.axaml.cs
+using System.ComponentModel;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
+using WalletWasabi.Fluent.ViewModels;
namespace WalletWasabi.Fluent.Views;
@@ -19,4 +21,22 @@ public class MainWindow : Window
this.AttachDevTools();
#endif
}
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ base.OnClosing(e);
+
+ if (Application.Current?.DataContext is ApplicationViewModel avm)
+ {
+ if (!Services.UiConfig.HideOnClose)
+ {
+ e.Cancel = !avm.CanShutdown();
+
+ if (e.Cancel)
+ {
+ avm.OnClosePrevented();
+ }
+ }
+ }
+ }
}

manage window close, app menu quit, tray menu quit, with prevent shutdown and prevent shutdown dialog.