Skip to content

8. GUI--Development

The simplest technology to develop a desktop application for windows is WinForms. In WinForms you have a visual designer, drag & drop your elements, make a double click and for example a button click handler is initialized and a code behind file connected and created to the UI. However, this simple and quick development comes with some disadvantages: it runs on Windows only, the UI is stored in a proprietary format and hence it is difficult to see differences in git, testing is difficult, loose coupling is difficult, and the UI looks old-fashioned.

The next big player was WPF, a modern, XAML-based (XML) and hence declarative coded UI framework. It exists since 2006, is widely used and addresses all the above-mentioned flaws of WinForms. It may be integrated (WebView) into Uno, but it is not designed to be cross-platform and hence, the UI feels just windowish on other systems. WPF is distributed under MIT license and its development is driven by Microsoft, with this roadmap.

On the mobile side Microsoft started with Xamarin.Forms. It was first released in 2014.

".NET MAUI is the evolution of Xamarin.Forms and uses the latest technologies for building native apps on Windows, macOS, iOS, and Android, abstracting them into one common framework built on .NET."1

Since 2013 there is a new player on the market: Avalonia. Avalonia is cross-platform by design, also XAML based, and driven by several companies, e.g. JetBrains. The interest is rising, see the image below.

Avalonia vs WPF
Is WPF Dead? - GitHub-Trends

However, Google Trends shows that WPF is still preferred.

But there are others too. Microsoft lists the following frameworks for desktop development

It was really difficult to choose a good UI framework for this course. I used the following criteria

  • development with and for Windows, Linux, Mac -- as I want to offer this course for all students, not only for those who have a Windows notebook
    • all support Windows, Mac
    • Avalonia, Uno support Linux out of the box
    • MAUI has a workaround for linux
  • XAML-based, as most of the above mentioned and good frameworks use XAML and switching to another framework will be easier
    • MAUI, Avalonia, Uno are XAML based
  • MVVM-support as this is a widely used pattern in the .Net world
    • MAUI, Avalonia, Uno make it easy to implement MVVM
  • good integration into Rider, as Visual Studio for Mac will end 2024, Rider has Resharper, a great tool for refactoring and code inspection -- VS Code has nothing compared to Resharper.
  • many controls,
  • modern and mature and well documented

I then checked XAML Framework Comparison 2023. My favorite was Uno, until I found Uno - Rider - supported platforms. So either Uno with VS Code and hence no Resharper or MAUI or Avalonia.

Architecture MAUI vs Avalonia
Architecture MAUI vs Avalonia

The primary difference between Avalonia UI and MAUI lies in how they draw the user interface. Avalonia UI employs a custom drawing engine powered by Skia (like Flutter), while MAUI uses native UI toolkits for each platform.

Avalonia is very similiar to Flutter, which you might have learned in HCI.

With the better support for Linux I decided to go for Avalonia.

Treat yourself to a motivational boost and watch this video 📹Unleashing the Power of Cross-Platform Development with Avalonia UI | .NET Conf 2023.

However, all frameworks are similar and it should be easy to switch from one to another if you follow the MVVM architecture described in the next sub section.

8.1 XAML - Extensible Application Markup Language

"Extensible Application Markup Language (XAML /ˈzæməl/ is a declarative XML-based language developed by Microsoft for initializing structured values and objects."

Below you will find a XAML example from XAML overview (WPF .NET).

<Page x:Class="index.Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Page1">

</Page>
<StackPanel>
    <Button Content="Click Me"/>
    <Button Background="Blue" Foreground="Red" Content="This is a button"/>
</StackPanel>

A XAML file typically comes in pairs, the XAML file defining how the view looks and the code behind file that implements how the view behaves. All your UI elements are available in the code behind file by their name.

Below you will find an example from Avalonia.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="AvaloniaApplication5.MainWindow">
  <Button Name="greetingButton" Click="GreetingButtonClickHandler">Hello World</Button>
</Window>
And the corresponding code behind file
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public void GreetingButtonClickHandler(object sender, RoutedEventArgs e)
    {
        greetingButton.Background = Brushes.Blue;
        // of course in this example you could have used the sender parameter
    }
}

8.2 Event Loop

Event Loop
Event Loop

Every event driven framework has an event loop. Events are a tap, a swipe or any other user interaction. The event loop must also handle returns/callbacks e.g. after loading data from a network.

Sometimes, like in WPF, "the applications start with two threads: one for handling rendering and another for managing the UI. The rendering thread effectively runs hidden in the background while the UI thread receives input, handles events, etc.

The UI thread queues work items inside an object called a Dispatcher. A Dispatcher provides services for managing the queue of work items for a thread.

The trick to building responsive, user-friendly applications is to maximize the Dispatcher throughput by keeping the work items small. This way items never get stale sitting in the Dispatcher queue waiting for processing. Any perceivable delay between input and response can frustrate a user.

What if your code involves a large calculation or needs to query a database on some remote server? Usually, the answer is to handle the big operation in a separate thread, leaving the UI thread free to tend to items in the Dispatcher queue. When the big operation is complete, it can report its result back to the UI thread for display."2

8.3 Event Routing

Event Routing

"A typical UI application contains many elements. These elements exist in an element tree relationship to each other. The event route can travel in one of two directions depending on the event definition, but generally the route travels from the source element and then "bubbles" upward through the element tree until it reaches the element tree root (typically a page or a window). This bubbling concept might be familiar to you if you have worked with the HTML DOM previously."3

8.4 MVVM

The Model-View-ViewModel is a widely used architectural pattern in the C# and .Net world, but also in other frameworks. It decouples the view from any logic and hence makes testability easier.

MVVM
The MVVM pattern

"At a high level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view. Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view.

The benefits of using the MVVM pattern are as follows:

  • If an existing model implementation encapsulates existing business logic, it can be difficult or risky to change it. In this scenario, the view model acts as an adapter for the model classes and prevents you from making major changes to the model code.
  • Developers can create unit tests for the view model and the model, without using the view. The unit tests for the view model can exercise exactly the same functionality as used by the view.
  • The app UI can be redesigned without touching the view model and model code, provided that the view is implemented entirely in XAML or C#. Therefore, a new version of the view should work with the existing view model.
  • Designers and developers can work independently and concurrently on their components during development. Designers can focus on the view, while developers can work on the view model and model components.

The key to using MVVM effectively lies in understanding how to factor app code into the correct classes and how the classes interact. The following sections discuss the responsibilities of each of the classes in the MVVM pattern.

8.4.1 View

The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations.

Tip

Avoid enabling and disabling UI elements in the code-behind.

Ensure that the view models are responsible for defining logical state changes that affect some aspects of the view's display, such as whether a command is available, or an indication that an operation is pending. Therefore, enable and disable UI elements by binding to view model properties, rather than enabling and disabling them in code-behind.

There are several options for executing code on the view model in response to interactions on the view, such as a button click or item selection. If a control supports commands, the control's Command property can be data-bound to an ICommand property on the view model. When the control's command is invoked, the code in the view model will be executed. In addition to commands, behaviors can be attached to an object in the view and can listen for either a command to be invoked or the event to be raised. In response, the behavior can then invoke an ICommand on the view model or a method on the view model.

8.4.2 ViewModel

The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed.

Tip

Keep the UI responsive with asynchronous operations.

Multi-platform apps should keep the UI thread unblocked to improve the user's perception of performance. Therefore, in the view model, use asynchronous methods for I/O operations and raise events to asynchronously notify views of property changes.

The view model is also responsible for coordinating the view's interactions with any model classes that are required. There's typically a one-to-many relationship between the view model and the model classes. The view model might choose to expose model classes directly to the view so that controls in the view can data bind directly to them. In this case, the model classes will need to be designed to support data binding and change notification events.

Each view model provides data from a model in a form that the view can easily consume. To accomplish this, the view model sometimes performs data conversion. Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. For example, the view model might combine the values of two properties to make it easier to display by the view.

It's also possible to use converters as a separate data conversion layer that sits between the view model and the view. This can be necessary, for example, when data requires special formatting that the view model doesn't provide.

8.4.3 Model

Model classes are non-visual classes that encapsulate the app's data. Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic. Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects."4

8.5 Data Binding

Data Binding
Data Binding

"Data binding is the process that establishes a connection between the app UI and the data it displays.

  • OneWay binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property. This type of binding is appropriate if the control being bound is implicitly read-only. For instance, you may bind to a source such as a stock ticker, or perhaps your target property has no control interface provided for making changes, such as a data-bound background color of a table. If there's no need to monitor the changes of the target property, using the OneWay binding mode avoids the overhead of the TwoWay binding mode.

  • TwoWay binding causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully interactive UI scenarios. Most properties default to OneWay binding, but some dependency properties (typically properties of user-editable controls such as the TextBox.Text and CheckBox.IsChecked default to TwoWay binding.

  • OneWayToSource is the reverse of OneWay binding; it updates the source property when the target property changes. One example scenario is if you only need to reevaluate the source value from the UI."5

8.5.1 INotifyPropertyChanged

The base for data binding is the interface INotifyPropertyChanged. "INotifyPropertyChanged is an interface provided by .NET that a class can implement to signal that a property has changed its value.

The INotifyPropertyChanged interface has one event member, PropertyChanged. When a property's value is changed, the object raises a PropertyChanged event to notify any bound elements that the property has changed.

public class MyViewModel : INotifyPropertyChanged
{
    private string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
In this code, whenever the Name property is set to a new value, the OnPropertyChanged method is called, which raises the PropertyChanged event. Any UI elements bound to this property will then update to reflect the new value."6

8.5.2 Commands

It is also possible to bind commands to buttons etc, i.e. within XAML you may define if a button is active and which code should run when the user clicks the button.

The ICommand interface is the code contract for commands that are written in .NET. It defines - Execute: The code to run. - CanExecute: Enable/disable the e.g. button. - CanExecuteChanged: Event to inform, that CanExecute changed.

8.6 Asynchronous Programming

You want to keep every UI responsive and hence not blocking and you want to run programs as fast as possible. Hence, whenever possible you should run processes that take time asynchronously.

Async Example
Task asynchronous programming model

The figure above illustrates the following code

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

"The async and await keywords in C# are the heart of async programming. By using those two keywords, you can use resources in .NET Framework, .NET Core, or the Windows Runtime to create an asynchronous method almost as easily as you create a synchronous method. Asynchronous methods that you define by using the async keyword are referred to as async methods.

The numbers in the diagram correspond to the following steps, initiated when a calling method calls the async method.

  1. A calling method calls and awaits the GetUrlContentLengthAsync async method.
  2. GetUrlContentLengthAsync creates an HttpClient instance and calls the GetStringAsync asynchronous method to download the contents of a website as a string.
  3. Something happens in GetStringAsync that suspends its progress. Perhaps it must wait for a website to download or some other blocking activity. To avoid blocking resources, GetStringAsync yields control to its caller, GetUrlContentLengthAsync. GetStringAsync returns a Task, where TResult is a string, and GetUrlContentLengthAsync assigns the task to the getStringTask variable. The task represents the ongoing process for the call to GetStringAsync, with a commitment to produce an actual string value when the work is complete.
  4. Because getStringTask hasn't been awaited yet, GetUrlContentLengthAsync can continue with other work that doesn't depend on the final result from GetStringAsync. That work is represented by a call to the synchronous method DoIndependentWork.
  5. DoIndependentWork is a synchronous method that does its work and returns to its caller.
  6. GetUrlContentLengthAsync has run out of work that it can do without a result from getStringTask. GetUrlContentLengthAsync next wants to calculate and return the length of the downloaded string, but the method can't calculate that value until the method has the string.

Therefore, GetUrlContentLengthAsync uses an await operator to suspend its progress and to yield control to the method that called GetUrlContentLengthAsync. GetUrlContentLengthAsync returns a Task to the caller. The task represents a promise to produce an integer result that's the length of the downloaded string."7

Warning

Do not use Task.run without await, as the compiler automatically wraps it in a try/catch and only with the await keyword, an exception will be rethrown and hence you may handle it properly. And as you may not await async void, do not implement async void or only in combination with" binding to async commands.


  1. Microsoft. Xamarin | open-source mobile app platform for .net. 2024. URL: https://dotnet.microsoft.com/en-us/apps/xamarin (visited on 29.02.2024). ↩

  2. Adegeo. Threading model - wpf .net framework. 2023. URL: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/threading-model?view=netframeworkdesktop-4.8 (visited on 01.03.2024). ↩

  3. Routed events | avalonia docs. 2024. URL: https://docs.avaloniaui.net/docs/concepts/input/routed-events#what-is-a-routed-event (visited on 01.03.2024). ↩

  4. Michael Stonis. Model-view-viewmodel - .net. 2022. URL: https://learn.microsoft.com/en-us/dotnet/architecture/maui/mvvm (visited on 01.03.2024). ↩

  5. Adegeo. Data binding overview - wpf .net. 2024. URL: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-8.0 (visited on 01.03.2024). ↩

  6. How to use inotifypropertychanged | avalonia docs. 2024. URL: https://docs.avaloniaui.net/docs/guides/data-binding/inotifypropertychanged (visited on 01.03.2024). ↩

  7. Bill Wagner. The task asynchronous programming (tap) model with async and await' - c#. 2023. URL: https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/task-asynchronous-programming-model?source=recommendations (visited on 01.03.2024). ↩