Avalonia UI has been a refreshing reprieve from my typical web development workflows. Of course, the web will always be my first love, but I’m enjoying the ease and intuitiveness of desktop app development. Additionally, the Avalonia team has put extraordinary work into making a truly cross-platform development experience. So if you want to build your first Avalonia-powered application, this post might be just for you.

I’ll be using the NXUI library written by Wiesław Šoltés. If you’re allergic to XAML, this library might be your detour to desktop development, as it relies on a fluent interface to define views while retaining the APIs that make XAML so expressive.

So, let’s get started!

What is NXUI?

NXUI is an attempt to bring the trend of minimalism to desktop application development built around the Avalonia UI API. As author, Wiesław Šoltés states in the NXUI readme:

Creating minimal Avalonia next generation (NXUI, next-gen UI) application using C# 10 and .NET 6 and 7

So what does the code of a next-generation Avalonia application look like?

Window Build() => Window().Content(Label().Content("NXUI"));

AppBuilder.Configure<Application>()
  .UsePlatformDetect()
  .UseFluentTheme()
  .StartWithClassicDesktopLifetime(Build, args);

Wow! That’s relatively minimal, but let’s write something more useful. How about a clock? It’s time to write some code (pun intended).

To start building your first NXUI application, you’ll need to add the following to a .NET console app’s .csproj.

<PackageReference Include="NXUI" Version="11.0.0-preview5" />

Note: the version might differ when searching on NuGet. Be sure to use the latest version of the package.

An Avalonia UI Clock App

NXUI relies heavily on Observable elements to hydrate dynamic values in a view. In the case of our demo, we’ll be using the System.Timers.Timer class to create a new string value using the current DateTime.Now result. Let’s see how you create an Observable<string> that returns values on a specified interval.

var currentTime = Observable.Create<string>(
    observer =>
    {
        var timer = new System.Timers.Timer {
            Interval = 250,
        };
        timer.Elapsed += (_, _) => observer.OnNext($"{DateTime.Now:hh:mm:ss tt}");
        timer.Start();
        return Disposable.Empty;
    });

When working with NXUI, you’ll often use the Observable.Create method for dynamic values. You can also refactor many calls into separate classes and helper methods.

Next, let’s Build our view, which will hold our time string.

Window Build() =>
    Window()
        .Width(400).Height(200).CanResize(false)
        .WindowStartupLocation(WindowStartupLocation.CenterScreen)
        .Content(
            Border()
                .Margin(25, 0, 25, 0)
                .Height(100)
                .CornerRadius(10)
                .BoxShadow(BoxShadows.Parse("5 5 10 2 Black"))
                .Background(Brushes.White)
                .Child(
                    TextBlock()
                        .Foreground(Brushes.Black)
                        .TextAlignmentCenter()
                        .ZIndex(1)
                        .FontSize(40)
                        .FontStretch(FontStretch.Expanded)
                        .VerticalAlignment(VerticalAlignment.Center)
                        // Set The Observable<string> to Text
                        .Text(currentTime)
                )
        );

We have a window with a single Border element, which wraps our TextBlock. The most important aspect of our code is that the call to TextBlock.Text takes our currentTime argument. As the timer ticks, the UI will update with our latest value.

The code for this demo is a tight 40 lines of code.

var currentTime = Observable.Create<string>(
    observer =>
    {
        var timer = new System.Timers.Timer {
            Interval = 250,
        };
        timer.Elapsed += (_, _) => observer.OnNext($"{DateTime.Now:hh:mm:ss tt}");
        timer.Start();
        return Disposable.Empty;
    });

Window Build() =>
    Window()
        .Width(400).Height(200).CanResize(false)
        .WindowStartupLocation(WindowStartupLocation.CenterScreen)
        .Content(
            Border()
                .Margin(25, 0, 25, 0)
                .Height(100)
                .CornerRadius(10)
                .BoxShadow(BoxShadows.Parse("5 5 10 2 Black"))
                .Background(Brushes.White)
                .Child(
                    TextBlock()
                        .Foreground(Brushes.Black)
                        .TextAlignmentCenter()
                        .ZIndex(1)
                        .FontSize(40)
                        .FontStretch(FontStretch.Expanded)
                        .VerticalAlignment(VerticalAlignment.Center)
                        // Set The Observable<string> to Text
                        .Text(currentTime)
                )
        );

AppBuilder
    .Configure<Application>()
    .UsePlatformDetect()
    .UseFluentTheme()
    .StartWithClassicDesktopLifetime(Build, args);

Running our code, we see a functioning clock application. Pretty cool! (The image reflects the clock from the final sample).

Avalonia Clock

Conclusion

The most fantastic part of Avalonia has been the rich ecosystem of folks building different ways to make the technology accessible. If you like XAML, then that’s great. If you want C#, there are also ways to access Avalonia. If you’re an F# person, there are also folks working on F# implementations of Avalonia. NXUI is one of many approaches in the Avalonia ecosystem. The fluent interface made it a little more accessible to discover the properties of components and how they might tie together.

If you’d like to play around with the code found in this post, I’ve pushed up a complete working Avalonia Clock sample at my GitHub repository.