Handling the application exit in Avalonia UI based .NET apps
Inhaltsverzeichnis
So you are developing an Avalonia UI 11 app and asking yourself: „How can I actually react to app exits?“. This could be fore example useful, if you want to like save the user configuration on exit. On today’s post I will show you a pretty easy example on how to create or react to some sort of global „Exit Event“.
Handling application closing events – a common task
Today I just stumbled upon the task of „How to handle the shutdown event of an Avalonia UI app?“ myself. I needed to save an Ini-File when the program was closing, to make sure, that the user configuration is persisted, before the application fully closes. I thought, well, I could just use the normal way – but then I remembered, that I’m using the Avalonia UI side of things, not like typical WPF or Windows Forms.
When googling myself I didn’t find pretty much what I wanted, because there was some split up information. Some things found in the Avalonia UI documentation, some things on usual places like stackoverflow, etc. With this post I’m sort of writing a reminder for myself and wanted to share it with the developer world as well, if you are having the same problem.
First – but failed – attempts for handling application exits
When thinking about like the typical old Windows Forms (Winforms) applications, you could just subscribe and handle the „Application.ApplicationExit“-Event. For sure, you could do things like handling single Form Closing events and if the corresponding form was the last one – well, then mostly the application exits as well. Trying this with Avalonia won’t work – as you might already found out for yourself.
No Windows Forms like Application.ApplicationExit?
But how about WPF’s Application.Exit?
Trying it the WPF style wouldn’t work too, as you don’t have the „Application.Exit“ event either. No such available event is listed when entering the letters as sub-thing for the Application class.
So there has to be another possibility to catch something like a global „Avalonia UI app exit event“. Let’s take a look at this in the next section. As always – feel free to comment if you have a better alternative solution.
A possible solution
In the end I came to the following solution for my own infrastructure / program setup. Maybe there’s another, maybe there’s a better one – feel free to comment, if you found another one. In the end, you have to decide yourself what’s a „best fit“ for your rules (like developing with the MVVM pattern, etc.).
Going into App.axaml
With Avalona UI you will have an App.axaml file. Just click it for example inside the solution folder of Visual Studio and navigate to the code behind by pressing F7 – or use any other known method for that. Being inside that raw file, it should like the following for a desktop driven app. Keep in mind, that I’m using a specific syntax for the if statement here. I’m also using a DI driven „get me a MainWindowViewModel“ approach – but that’s not the key here anyways…
// some imports // namespace etc public partial class App : Application { public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.MainWindow = new MainWindow() { DataContext = Locator.Current.GetService<MainWindowViewModel>(), }; } base.OnFrameworkInitializationCompleted(); } }
Checking on Avalonia UI ApplicationLifetime’s
If you take a closer look (or search) at the documentation for Avalonia UI, you will find something called „ApplicationLifetime“. Please refer to the corresponding article / section to get more detailed information on that, I will focus more on our problem here. You will basically find code like the one I’ve posted above there. But the most important part is that overriden „OnFrameworkInitializationCompleted“ method.
We can check the „ApplicationLifetime“ Property of the „Avalonia.Application“ class and work further on that. In the code above (and in the example code from the documentation) we tried to check, if the current „ApplicationLifetime“ is a „IClassicDesktopStyleApplicationLifetime“. If so, we have it assigned to a variable called desktop – and this is where things get interesting.
One publisher, multiple subscribers
The „desktop“ variable implementing the mentioned interface provides an event called „ShutdownRequested“ – tada, exactly what we wanted, right!? Usually you would now try to access this stuff from different places in the form of:
But that doesn’t really work, at least with the search and tries I did. I couldn’t figure out how to retrieve the current app lifetime for example in a ViewModel to say „Hey, if the app get’s closed, do this or that“. So in the end, I made it work like this, but manually. Append your custom evenhandler to the „ShutdownRequested“ event of the casted „ApplicationLifetime“:
// the other code inside the override "OnFrameworkInitializionCompleted" // method of the App class desktop.ShutdownRequested += This_ShutdownRequested; // other code // as a separate method, define the handler // optionally with some sort of debug output for testing, or use step debugging... protected virtual void This_ShutdownRequested(object? sender, ShutdownRequestedEventArgs e) { Debug.WriteLine($"App.{nameof(This_ShutdownRequested)}"); OnShutdownRequested(e); } // in conjunction with typical .NET events, we will create the used "OnXy" method protected virtual void OnShutdownRequested(ShutdownRequestedEventArgs e) { ShutdownRequested?.Invoke(this, e); } // and define the event public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
Subscribing to the Shutdown event globally
If you are inside for example a ViewModel, you can now access and subscribe the defined event. Just grab the current „App“ class instance by accessing the „Current“ property of the „Application“ class. Then make a null check and subscribe the usual C# event handling way. You could do this for example inside a constructor of a viewmodel and unsubscribe on deactivation.
// somwhere executable like in a ViewModel constructor App app = Application.Current as App; if (app != null) app.ShutdownRequested += This_ShutdownRequested; // declaring the handler method private void This_ShutdownRequested(object? sender, ShutdownRequestedEventArgs e) { Debug.WriteLine("App shutting down"); }
Cancelling the close mechanism
If you want to cancel the closing of the application, you can do it like the usual way. The „ShutdownRequestedEventArgs“ parameter „e“ has a typical „e.Cancel“ property. Just set it to true inside the handler and the app exit will be cancelled.
Full code for App.axaml.cs
Here you can find the complete code letting your „App“ class provide a more centralized „Shutdown“, „Exit“ or „Closing“ event (based on your naming) in your Avalonia UI app. Keep in mind that you will need to adjust most of the namespaces and that only the class / the handling of the event is mandatory for you:
using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using rskibbe.Motraso.GUI.ViewModels; using rskibbe.Motraso.GUI.Views; using Splat; using System; using System.Diagnostics; namespace rskibbe.Motraso.GUI; public partial class App : Application { public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.ShutdownRequested += This_ShutdownRequested; desktop.MainWindow = new MainWindow() { DataContext = Locator.Current.GetService<MainWindowViewModel>(), }; } base.OnFrameworkInitializationCompleted(); } protected virtual void This_ShutdownRequested(object? sender, ShutdownRequestedEventArgs e) { Debug.WriteLine($"App.{nameof(This_ShutdownRequested)}"); OnShutdownRequested(e); } protected virtual void OnShutdownRequested(ShutdownRequestedEventArgs e) { ShutdownRequested?.Invoke(this, e); } public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested; }