Tricks mit XAML-Namespaces in Avalonia

Avalonia nutzt XAML als Beschreibungssprache für UIs. XAML wurde mit WPF eingeführt und hat sich seitdem im .NET-Ökosystem gut verbreitet und etabliert. Die Grundidee von XAML ist denkbar einfach. Es nutzt XML als Basis, erweitert es aber an einigen Stellen mit zusätzlichen Features für die UI-Entwicklung. Ein gutes Beispiel solcher Features sind MarkupExtensions. Viele von uns (inkl. mir selbst) arbeiten gerne mit XAML, an einigen Stellen sorgt es aber für mehr Tipparbeit, als es sein müsste. Ein Beispiel dafür sind die teilweise vielen und langen Imports für XAML-Namespaces, die am root-Knoten geschrieben werden müssen. .NET MAUI adressiert dieses Problem im aktuellen Release [1], doch gibt es auch Wege in Avalonia? In diesem Artikel möchte ich einige Tricks zeigen, um speziell mit diesem Problem in Avalonia eleganter umzugehen.

Wo haben wir viel Tipparbeit?

Die Funktionsweise für das Mapping von .NET Namespaces auf XAML-Namespaces hat sich seit der Einführung von WPF nicht oder zumindest nicht wesentlich geändert. Wenn man sich eine typische XAML-Datei wie im folgenden Codeausschnitt ansieht, erkennt man schnell zwei verschiedene Typen von Namespace-Imports:

  • Import aus dem Avalonia-Framework (hier der Namespace https://github.com/avaloniaui)
  • Allgemeine Imports für die XAML-Sprache (z. B. http://schemas.microsoft.com/winfx/2006/xaml) – diese gelten seit der Einführung von WPF
  • Mapping eigener Namespaces (hier z. B. clr-namespace:RolandK.AvaloniaExtensions.TestApp.Views)

Auf die ersten beiden haben wir leider keinen Einfluss. Das ist auch nicht schlimm, da diese beim Erstellen einer XAML-Datei bereits durch das Template erzeugt wurden. Interessanter wird es beim Letztgenannten – beim Import eigener Namespaces. Diese müssen wir selbst schreiben und je nach Applikation und XAML-Datei haben wir i. d. R. mehrere davon. Die Schreibweise „clr-namespace:<namespace-name>“ ist dazu alles andere als kurz. Somit entsteht an dieser Stelle regelmäßig lästige Tipparbeit.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ext="https://github.com/RolandK.AvaloniaExtensions"
        xmlns:views="clr-namespace:HappyCoding.AvaloniaNamespaceMappings.Views"
        xmlns:local="clr-namespace:HappyCoding.AvaloniaNamespaceMappings"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="RolandK.AvaloniaExtensions.TestApp.MainWindow"
        Title="{Binding Path=Title}"
        ExtendClientAreaToDecorationsHint="True"
        DataContext="{ext:CreateUsingDependencyInjection {x:Type local:MainWindowViewModel}}"
        d:DataContext="{x:Static local:MainWindowViewModel.DesignViewModel}">
    <!-- ... -->
</Window>

Eigene XAML-Namespaces definieren

XAML stellt schon seit WPF einen Weg bereit, mehrere .NET Namespaces auf bestimmte XAML-Namespaces zu mappen – und zwar nicht im XAML direkt, sondern mittels Attribute im C#-Code. Nachfolgender Codeausschnitt zeigt uns das an einem kleinen Beispiel. Gegeben ist eine Applikation, die einen root-Namespace, einen View-Namespace und einen Namespace für Converter hat. Wir können dem Compiler mittels dem Attribut XmlnsDefinitionAttribute sagen, dass all diese Namespaces auf den XAML-Namespace „local-application“ mappen sollen. Das Attribut XmlnsPrefixAttribute gibt schließlich noch einen Standard-Prefix für unseren XAML-Namespace an. Dieser ist kein muss, IDEs wie Rider können diesen aber nutzen, wenn der XAML-Namespace per Context Action automatisch eingebunden wird.

[assembly: XmlnsDefinition(
    "local-application", 
    "HappyCoding.AvaloniaNamespaceMappings")]
[assembly: XmlnsDefinition(
    "local-application", 
    "HappyCoding.AvaloniaNamespaceMappings.Views")]
[assembly: XmlnsDefinition(
    "local-application", 
    "HappyCoding.AvaloniaNamespaceMappings.Converters")]
[assembly: XmlnsPrefix("local-application", "app")]

Mit den Mappings können wir die Namespace-Imports deutlich abkürzen. Nachfolgend dazu ein Vergleich anhand des letzten Beispiels.

<!-- vorher -->
<Window ...
        xmlns:views="clr-namespace:HappyCoding.AvaloniaNamespaceMappings.Views"
        xmlns:local="clr-namespace:HappyCoding.AvaloniaNamespaceMappings"
        ...>
<!-- nachher -->
<Window ...
        xmlns:app="local-application"
        ...>

Wie wir den eigenen XAML-Namespace benennen, ist dabei völlig offen. In XML gibt es zwar allgemein die Konvention, dafür URLs zu nutzen, dies ist aber kein Muss. Ich habe daher der Einfachheit halber „local-application“ verwendet.

Den XAML-Namespace von Avalonia erweitern

Daneben gibt es noch einen anderen Trick, der uns die Angabe der Namespace-Imports für unsere eigenen .NET Namespaces komplett erspart. Das Attribut XmlnsDefinition erlaubt uns, .NET Namespaces zu beliebigen XAML-Namespaces zu mappen. Das Wort „beliebig“ schließt dabei auch alle bereits vorhandenen XAML-Namespaces ein – wie den XAML-Namespace von Avalonia. Nachfolgender Codeausschnitt zeigt, wie wir unsere .NET Namespaces in den XAML-Namespace von Avalonia mappen können. Vorteil dieser Variante: Wir brauchen unsere eigenen Namespaces gar nicht mehr in XAML importieren, da sie dann schon über den XAML-Namespace von Avalonia kommen. Ein möglicher Nachteil wären Namenskonflikte, daher muss bei dieser Variante etwas aufgepasst werden.

[assembly: XmlnsDefinition(
    "https://github.com/avaloniaui", 
    "HappyCoding.AvaloniaNamespaceMappings")]
[assembly: XmlnsDefinition(
    "https://github.com/avaloniaui", 
    "HappyCoding.AvaloniaNamespaceMappings.Views")]
[assembly: XmlnsDefinition(
    "https://github.com/avaloniaui", 
    "HappyCoding.AvaloniaNamespaceMappings.Converters")]

Fazit

Wir haben in diesem Artikel zwei Wege kennengelernt, effizienter mit Namespace-Imports in XAML umzugehen. Ich persönlich nutze dabei meist den Weg mit einem eigenen Namespace. Den Weg, den XAML-Namespace von Avalonia zu erweitern, habe ich auch schon einige Male gesehen. Für welchen man sich entscheidet, ist aus meiner Sicht Geschmackssache.

Verweise

Ebenfalls interessant

Schreibe einen Kommentar

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