ValueConverter in Avalonia – 3 Wege, sie in XAML einzubinden

Avalonia hat sich als modernes, Cross-Platform-UI‑Framework für .NET etabliert. Ein zentrales Element bei der Datenbindung ist dabei der ValueConverter: eine kleine Logikschicht, die Werte und Objekte zwischen ViewModel und UI umwandeln kann. In anderen auf XAML basierenden Frameworks wie WPF ist das Konzept bereits seit Jahrzehnten bekannt – in Avalonia funktioniert es grundsätzlich ähnlich und wird auch häufig genutzt. Für die Art, wie wir ValueConverter im XAML-Code einbinden, gibt es allerdings wie so oft mehrere verschiedene Wege. Diesen Artikel möchte ich nutzen, um einige davon zu zeigen. Leser können sich damit eine Meinung bilden, welche davon für sie selbst die sinnvollste ist.

Eigener ValueConverter als Beispiel

Als Beispiel dieses Artikels soll ein eigener ValueConverter dienen, der eine Versionsnummer in einen Brush umwandelt. Versionen kleiner als die Referenz-Version sollen Rot zurückgeben. Entspricht die Version der Referenz-Version, dann Gelb und ist die Version größer, dann Grün. Das Beschriebene in Code gegossen sieht dann wie im nachfolgenden Codeausschnitt aus.

public class VersionStringToBrushConverter : IValueConverter
{
    private static readonly Version VERSION_1_0 = new Version(1, 0, 0, 0);
    
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is not string valueString)
        {
            return AvaloniaProperty.UnsetValue;
        }

        if (!Version.TryParse(valueString, out var parsedVersion))
        {
            return AvaloniaProperty.UnsetValue;
        }

        if (parsedVersion < VERSION_1_0) { return Brushes.Red; }
        if (parsedVersion == VERSION_1_0) { return Brushes.Yellow; }
        return Brushes.Green; 
    }

    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        // Not supported
        return BindingOperations.DoNothing;
    }
}

Klassisch als XAML-Ressource einbinden

Der klassische Weg in XAML ist die Verwendung von XAML-Ressourcen. Damit wird der Converter einmalig in den Ressourcen registriert und kann anschließend referenziert werden. „Klassisch“ als Begriff nutze ich an dieser Stelle deswegen, weil es so seit den ersten Tagen von WPF allgemein üblich ist. Nachfolgender Codeausschnitt zeigt ein einfaches Beispiel innerhalb eines UserControls. Alternativ kann die XAML-Ressource auch an zentraler Stelle, etwa der App.axaml definiert werden.

<UserControl ...>
    <UserControl.Resources>
        <local:VersionStringToBrushConverter x:Key="ConverterVersionStringToBrush" />
    </UserControl.Resources>

    <!-- ... -->
    
    <Rectangle Height="10"
               HorizontalAlignment="Stretch"
               Margin="5"
               Fill="{Binding 
                   Path=VersionString, 
                   Converter={StaticResource ConverterVersionStringToBrush}
               }" />

    <!-- ... -->
</UserControl>

Der Vorteil dieser Variante ist, dass es mit XAML-Ressourcen auf einen Standardmechanismus von Avalonia und allgemein von auf XAML basierten Frameworks aufsetzt. Der Nachteil aus meiner Sicht ist der, dass eigene ValueConverter an zwei Stellen definiert werden müssen. Zum einen die ValueConverter Klasse selbst, zum anderen innerhalb der XAML-Ressourcen. Ich nutze diesen Weg in meinen Projekten daher praktisch nie.

Als statische Variable per x:Static einbinden

Statt die Instanz des ValueConverter in den XAML-Ressourcen zu erstellen, kann man alternativ eine statische Variable (oder Property) im Code bereitstellen und diese über x:Static vom XAML-Code aus referenzieren. Das produziert zwar etwas mehr Boilerplate-Code, spart uns aber die XAML-Ressource. Die statische Variable kann dabei in einer eigenen Hilfsklasse liegen oder wie im nachfolgenden Beispiel alternativ direkt im ValueConverter.

public class VersionStringToBrushConverter : IValueConverter
{
    public static readonly VersionStringToBrushConverter Instance = new VersionStringToBrushConverter();
    
    // ...
}
<UserControl ...>
    <!-- ... -->
    
    <Rectangle Height="10"
               HorizontalAlignment="Stretch"
               Margin="5"
               Fill="{Binding 
                   Path=VersionString, 
                   Converter={x:Static local:VersionStringToBrushConverter.Instance}
               }" />

    <!-- ... -->
</UserControl>

Der Vorteil dieser Variante ist ebenfalls, dass mit x:Static auf einen Standardmechanismus von Avalonia zurückgegriffen wird. Zudem nutzt Avalonia selbst auch diesen Weg an verschiedenen Stellen, etwa bei der Klasse StringConverters [1]. Letzteres ist eine Hilfsklasse, in der mehrere Varianten des ValueConverters bereitgestellt werden.

Als MarkupExtension einbinden

Neben den beiden vorgestellten Wegen gibt es noch einen weiteren Trick: Über eine MarkupExtension. MarkupExtensions sind ein einfacher Weg, innerhalb von XAML dynamisch erzeugte Werte und Objekte einzufügen. Binding, StaticResource oder DynamicResource sind regelmäßig genutzte MarkupExtensions, die uns Avalonia von Haus aus zur Verfügung stellt. Wir können aber auch eigene MarkupExtensions entwickeln, indem wir von der Klasse MarkupExtension erben. Wenn nun der ValueConverter selbst von MarkupExtension erbt, kann dieser sich selbst zurückgeben. Im nächsten Codeausschnitt dazu ein Beispiel.

public class VersionStringToBrushConverter : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    // ...
}
<UserControl ...>
    <!-- ... -->
    
    <Rectangle Height="10"
               HorizontalAlignment="Stretch"
               Margin="5"
               Fill="{Binding 
                   Path=VersionString, 
                   Converter={local:VersionStringToBrushConverter}
               }" />

    <!-- ... -->
</UserControl>

Insbesondere im XAML-Code sieht diese Variante schon deutlich schlanker aus. Ein kleiner Nachteil dieser Variante ist, dass an jeder Stelle, an der man die MarkupExtension nutzt, auch ein Objekt der MarkupExtension erzeugt wird. Nutzung von statischen Variablen ist demgegenüber effizienter, da stets auf die gleiche Instanz des ValueConverters verwiesen wird.

Fazit

In diesem Artikel haben wir drei Wege kennengelernt, ValueConverter in XAML einzubinden. Die erste davon, also über Ressourcen, benutzte ich schon lange nicht mehr. Dieser Weg sorgt dafür, dass man den ValueConverter an zwei verschiedenen Stellen definieren muss: Innerhalb der ValueConverter Klasse selbst und in den Ressourcen. Die anderen beiden Varianten mittels statischer Variable oder MarkupExtension sind dagegen etwas eleganter. Statische Variablen sind sicherlich verbreiteter als via MarkupExtensions. Letztere wirken innerhalb des XAML-Codes aber dafür am saubersten. Der vollständige Beispiel-Code zu diesem Artikel ist auf GitHub unter [2] zu finden.

Verweise

  1. Built-in Data Binding Converters
    https://docs.avaloniaui.net/docs/reference/built-in-data-binding-converters
  2. Beispiel-Code zu diesem Artikel
    https://github.com/RolandKoenig/HappyCoding/tree/main/2025/HappyCoding.AvaloniaReferencingValueConverters

Ebenfalls interessant

  1. Als .NET Entwickler auf dem MacBook Pro
    https://www.rolandk.de/wp-posts/2024/05/als-net-entwickler-auf-dem-macbook-pro/
  2. Avalonia UI – Per MarkupExtension auf das Betriebssystem eingehen
    https://www.rolandk.de/wp-posts/2025/01/avalonia-ui-per-markupextension-auf-das-betriebssystem-eingehen/
  3. GPXviewer 2 – Mit Avalonia UI auf mehrere Plattformen
    https://www.rolandk.de/wp-posts/2025/01/gpxviewer-2-mit-avalonia-ui-auf-mehrere-plattformen/

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden.