One advantage of the new .NET Core (now .NET) apps is that they are cross platform. Once you create an app, the same code will run on Windows, Mac or Linux with no change. But there are some exceptions to this: WPF or WinForms apps are only for Windows. You cannot create a WPF app and run it on Linux, for example.
If you want to create a cross platform app, you will have to use another technology, like Blazor or MAUI. Blazor UI is based on Razor components and it's not compatible with XAML, so it may be very difficult to convert. MAUI, on the other side, uses XAML and can be used to port your app to Mac, iOS or Android. But MAUI apps don't run on Linux. If you want a Linux app, this is not the way. You can use Uno Platform (see my article here), but this is a Web technology (it uses WebAssembly in the browser), and you won't be able to create a desktop app (yes, you can create a PWA, but technically, it's not a desktop app). The other option is to use Avalonia UI.
In this article we will show how to convert an existing WPF app to Avalonia UI. We will use the project described in my MVVM Community toolkit 8.0 article. This project has some interesting features to explore:
- It uses a .NET Standard library for the repository
- It has a Datagrid to display the data
- It uses the MVVM pattern and the MVVM Community toolkit NuGet package
Avalonia UI is an open source cross platform framework for .NET to develop cross platform apps using XAML. To use it, you need to install the Avalonia project templates with:
Once you do it, you can create a new basic project with:
When you run it, you will see a basic app:
The generated code has these differences:
- The extension for the view files is axaml instead of xaml (and the code behind extension is axaml.cs)
- The default namespace for the view files is https://github.com/avaloniaui instead of http://schemas.microsoft.com/winfx/2006/xaml/presentation
- The project includes the Avalonia, Avalonia.Desktop, Avalonia.Diagnostics and XamlNameReferenceGenerator NuGet packages
- The project targets net6.0 (or net7.0), instead of net6.0-windows
- There is no need to include the UseWPF clause in the project file
- The Program.cs file and Main method are explicit
- There is some initialization code in App.axaml.cs
Apart from that, designing the UI and the C# code aren't much different from the standard WPF app.
For this app, we will use Visual Studio 2022 and the Avalonia Extension. This extension will provide all templates and a designer for the views. If you don't want to use Visual Studio, you can use VS Code, but you won't have the visual designer. In Visual Studio, go to Extensions/Manage Extensions and install the Avalonia for Visual Studio 2022 extension:
Let's start converting our app. We have two approaches, here: convert our app in place, making changes in the files as needed, or create a new basic Avalonia project and add the features incrementally. I prefer to use the second approach, in this case all the basic infrastructure is already set and we can make sure that things are running while we are adding the features. In the in place conversion, it's an all-or-nothing, and at the end we may not have any clue of what we've missed, in case it doesn't run.
The first step is to clone our app from https://github.com/bsonnino/MVVMToolkit8.git. Then, we will create our app in Visual Studio:
That will create a new basic app. If you open MainWindow.axaml, you will see the code and the visual designer:
Let's start converting our app. The first step is to add the two NuGet packages, CommunityToolkit.Mvvm and Microsoft.Extensions.DependencyInjection.
Then, copy the CustomerLib folder with all files to the folder of the Avalonia solution. We will use this project as is, as it's a .NET Standard project and it can be used by Avalonia unchanged. In the solution explorer, add an existing project and select the CustomerLib.csproj file. That will add the lib to our solution. In the main project, add a project reference and add the CustomerLib project:
Then, copy the ViewModel folder to the project folder, it will appear in the solution explorer. Open MainViewModel.cs, you will see an error in ColletionViewSource:
That's because the CollectionViewSource class doesn't exist in Avalonia and we need to replace it with this code:
Instead of using the WPF CollectionViewSource class, we are creating our filter and using it before displaying the data. Just to check, we can copy the Test project to the solution folder, add it to the current solution and run the tests to check. For that, we must do the following changes:
- Change the Target Framework in the csproj file to .net6.0
- Change the reference for the main project to MvvmAvalonia
Once we do that, we can compile the project, but we get the errors for the CollectionViewSource. For that, we must change the tests to:
Now, when we run the tests, they all pass and we can continue. We will start adding the UI to the main window:
There is an error with the DataGrid. That's because we need to add the package Avalonia.Controls.Datagrid. Once we add that, we can see some other errors:
- The ItemsSource property has been changed to Items
- The columns don't have the Name field and should be removed
- The CanUserAddRows and CanUserDeleteRows do not exist and should be removed
- We should add the themes for the DataGrid in App.axaml:
We can also see that this code is missing the Detail control. Add to the project a new item of type UserControl (Avalonia) and add the content from the original project:
We must remove the , ValidatesOnExceptions=true, NotifyOnValidationError=true from the code, as it's not available in Avalonia. Then, we should add the correct using clause in the main xaml:
Once we do that and we run, we can see the UI (but not the data):
For the data, we must add the configuration for the services, in App.axaml.cs:
Then, we must set the DataContext on MainWindow.axaml.cs:
Now, when we run the code, we can see it runs fine:
We've ported our WPF project to Avalonia, now it's ready to be run on Linux. We'll use WSLg (Windows Subsystem for Linux GUI) to run the app. Just open a Linux tab on terminal and cd to the project directory (the drive is mounted on /mnt/drive, like /mnt/c) and run the app with dotnet run
. You should have something like this:
As you can see, porting a WPF app to Avalonia requires some changes, but most of the code is completely portable. If you want to ease the process, you can move the non-UI code to .NET Standard libraries and use them as-is.
All the source code for the project is at https://github.com/bsonnino/MvvmAvalonia
If `NotifyOnValidationError=true ` is a must, how to handle it in Avalonia?
You can check this: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation