This article is part of F# Advent calendar in English 2017.
Introduction
Avalonia is a cross-platform UI .NET framework inspired by WPF and built around XAML and bindings. Why use another UI framework, you would ask? F# already has solid WPF-based infrastructure: FsXAML, FSharp.ViewModule and pretty awesome Gjallarhorn.Bindable.WPF. There is also F# support in Xamarin which allows you write UI applications for mobile devices.
But what if you need to build a cross-platform desktop applicaton (not Windows-only as in case of WPF)? Maybe you also want to use the modern .NET Core platform? Run the same UI code on mobile devices? Avalonia for the rescue!
Let’s start
OK, let’s start with an empty console .NET Core application. I usually use .NET Core CLI and wonderful Ionide extension for code editing (thanks a lot Krzysztof!). Works like a charm.
$ mkdir avalonia-fsharp-example
$ cd avalonia-fsharp-example
$ dotnet new console -lang f#
Then add Avalonia packages to the project.
$ dotnet add package Avalonia
$ dotnet add package Avalonia.Desktop
All native dependencies for your platform will be installed automagically.
Then you need to create the core of our application and initialize Avalonia. Create two files:
// App.fs
namespace AvaloniaFsharpExample
open Avalonia
open Avalonia.Markup.Xaml
type App() =
inherit Application()
override this.Initialize() =
AvaloniaXamlLoader.Load(this)
<!-- AvaloniaFsharpExample.App.xaml -->
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles>
</Application>
Hopefully you remember how to use OOP features in the F#! App
type is the core of the application – subtype of Avalonia’s Application
class. Avalonia uses XAML files for UI design: controls, windows, styles and bindings. In 2017, XAML may look a little clunky, but I still found it very convenient for UI design and especially if you already familiar with WPF or Xamarin. So you need to override Initialize
method and load App.xaml
file containing style settings for our application:
AvaloniaXamlLoader.Load(this)
It loads an appropriate .xaml
for our type. I’ll describe how it works in the next section. For now you need to create main window of the application.
In the main window, we are going to show a classical “hello world” text. Let’s design it!
<!-- AvaloniaFsharpExample.MainWindow.xaml -->
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AvaloniaFsharpExample">
Hello World from Avalonia and F#!
</Window>
You also need to load the XAML file into the application:
// MainWindow.fs
namespace AvaloniaFsharpExample
open Avalonia.Controls
open Avalonia.Markup.Xaml
type MainWindow () as this =
inherit Window()
do this.InitializeComponent()
member this.InitializeComponent() =
AvaloniaXamlLoader.Load(this)
The logic is the same as for App.fs
– we’re loading a XAML file during the type initialization. Final step required to unite all the parts of the application is to write a proper entry point:
// Program.fs
open System
open Avalonia
open AvaloniaFsharpExample
[<EntryPoint>]
let main argv =
AppBuilder.Configure<App>()
.UsePlatformDetect()
.Start<MainWindow>()
0
Just as simple as “configure app using appropriate backend and then start with MainWindow
”. What does mean “backend”? At the lowest level, Avalonia uses different subsystems to work with windows and draw controls on them. Those parts are platform specific, but backend can be automagically detected. You can read more about Avalonia’s architecture in the documentation (a little outdated).
Say hello
So, can we finally run the application? Not yet. AvaloniaXamlLoader
that we see before in the App
and MainWindow
types requires our XAML files to be placed in the binary resource section. To do that, we should to add <EmbeddedResource>
items to our project:
<ItemGroup>
<EmbeddedResource Include="AvaloniaFsharpExample.MainWindow.xaml">
<LogicalName>AvaloniaFsharpExample.MainWindow.xaml</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="AvaloniaFsharpExample.App.xaml">
<LogicalName>AvaloniaFsharpExample.App.xaml</LogicalName>
</EmbeddedResource>
</ItemGroup>
In the resource section, these names should be formated as [namespace].[type].xaml
, e.g. AvaloniaFsharpExample.MainWindow.xaml
. It seems on Windows and MacOS resource embedding behaviours are different, so you need use both explicit file name and LogicalName
in project (I’m going to create issue about it).
And don’t forget to add source code files into project. Remember about the files order!
<ItemGroup>
<Compile Include="MainWindow.fs" />
<Compile Include="App.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
Finally we can run it!
$ dotnet run
You will see the following window:
Please note if you’re MacOS user you need to install gtk+3
via brew
. It seems this issue is fixed in the nightly build of Avalonia.
Visual designer
If you’re a Visual Studio user, you will probably like Avalonia for Visual Studio extension. It contains visual designer for Avalonia XAML similar to WPF XAML designer.
Unfortunately, according to this issue, XAML designer doesn’t yet work on pure .NET Core applications. Therefore, to make it work, you need to add .NET Framework target to our fsproj
.
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
Make sure that you use TargetFramework_s_
. Then open project with Visual Studio and you will see following:
That’s also possible because of hard work of Visual F# contributors and MSFT team. Thanks you guys!
Conclusion
Hope you enjoyed this introductory blog post. What’s next? Please check this article by Reed Copsey about functional MVU approach for Avalonia.
You can find code in repository.
I also want to say thanks to Avalonia team for great job.