XAML Islands:
WPF und Windows Forms

Mit Windows 10 Controls Anwendungen modernisieren
10
Dez

XAML Islands: WPF und Windows Forms

Als XAML Islands wird eine mit Windows 10 Version 1903 eingeführte Technologie bezeichnet, mit der sich moderne UWP Controls mit Fluent Design in WPF- und Windows-Forms-Applikationen einbinden lassen. Damit ist es möglich, bestehende Anwendungen zu modernisieren, ohne dass diese gleich von Grund auf neu geschrieben werden müssen. In diesem Artikel lesen Sie, wie Sie XAML Islands einsetzen, und Sie erfahren auch, wie der zukünftige Weg der Universal Windows Platform aussieht.

Viele Unternehmen haben zahlreiche .NET-Desktopanwendungen im Einsatz, die mit Windows Forms und WPF basierend auf dem .NET Framework entwickelt wurden. Mit .NET Core und XAML Islands lassen sich diese Anwendungen modernisieren. Bevor wir uns in diesem Artikel XAML Islands widmen, werfen wir einen kurzen Blick auf die Migration von .NET-Framework-Anwendungen nach .NET Core.

Migration nach .NET Core

Mit .NET Core 3.0 hat Microsoft sowohl Windows Forms als auch WPF auf .NET Core portiert und darüber hinaus auch den Quellcode auf GitHub veröffentlicht.

Um XAML Islands in einer WPF oder Windows Forms App zu verwenden, ist eine Portierung der eigenen App auf .NET Core nicht zwingend erforderlich, aber als Teil der Modernisierung empfohlen. .NET Core unterstützt mit XAML Islands mehr Szenarien und hat darüber hinaus noch weitere Vorteile. Beispielsweise lassen sich nur mit .NET Core die neuesten, mit C# 8.0 eingeführten Sprachfeatures verwenden. Im .NET Framework stehen diese Sprachfeatures nicht zur Verfügung.

Um eine einfache WPF-Anwendung nach .NET Core zu portieren, ist lediglich eine Anpassung der .csproj-Datei notwendig. Üblicherweise kann der bestehende Inhalt komplett gelöscht und durch den Inhalt aus Listing 1 ersetzt werden.

Listing 1: .NET-Core-Projektdatei

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
</Project>

Listing 1 enthält das sogenannte SDK-Style-Projektformat, das für ein .NET-Core-Projekt zwingend notwendig ist. Das SDK-Style-Projektformat ist daran zu erkennen, dass auf dem Project-Element, das die Wurzel des Dokuments darstellt, das Sdk-Attribut gesetzt ist. In der Projektdatei in Listung 1 wird mit diesem Sdk-Attribut das Windows Desktop SDK von .NET Core verwendet. Zudem ist in der Projektdatei in Listing 1 .NET Core 3 als Target Framework angegeben. Mit dem UseWPF-Element werden in einem .NET-Core-Projekt die Referenzen für die WPF Assemblies hinzugefügt. Wäre es ein Windows-Forms-Projekt, wäre der einzige Unterschied in der Projektdatei, dass statt eines UseWPF-Elements eben ein UseWindowsForms-Element verwendet wird, um damit die Windows Forms Assemblies zu referenzieren.

Verschaffen Sie sich den Zugang zur .NET- und Microsoft-Welt mit unserem kostenlosen Newsletter!

Nach der Anpassung der .csproj-Datei auf das minimale, in Listing 1 dargestellte Format, müssen natürlich vorherige Projekt- und NuGet-Referenzen wieder hinzugefügt werden, was wie gewohnt über den Solution Explorer von Visual Studio gemacht werden kann. Damit ist die Migration von .NET Framework nach .NET Core abgeschlossen.

In diesem Artikel wird für XAML Islands eine .NET-Core-3.0-WPF-Anwendung verwendet. Bevor es losgeht, werfen wir einen Blick auf die XAML-Islands-Grundlagen.

XAML-Islands-Grundlagen

Mit Windows 10 Version 1903, dem sogenannten May 2019 Update, wurde die XAML Islands API eingeführt. Dabei handelt es sich um eine Windows-Schnittstelle. Mit ihr lässt sich das Aussehen und die Funktionalität einer WPF oder Windows Forms App mit Windows-10-UI-Features erweitern. Die Windows-10-UI-Features werden in Form von UWP Controls bereitgestellt. Es lassen sich beliebige UWP Controls in der bestehenden .NET-Anwendung hosten, wie beispielsweise das Windows 10 MapControl zum Anzeigen von Landkarten oder das InkCanvas Control, um die Windows-10-Stifteingabe optimal in der eigenen .NET-Desktop-Anwendung zu unterstützen.

Prinzipiell lässt sich jedes UWP Control in einer WPF- oder Windows-Forms-Anwendung hosten, wenn es von der UWP-Basisklasse Windows.UI.Xaml.UIElementableitet. Dabei werden folgende Szenarien unterstützt:

  1. das Hosten von 1st Party UWP Controls, die von Microsoft über das Windows SDK oder die sogenannte WinUI-Bibliothek bereitgestellt werden (mehr zur sogenannten WinUI-Bibliothek erfahren Sie später in diesem Artikel, wenn es um die Zukunft der UWP geht)
  2. das Hosten von benutzerdefinierten UWP Controls, wie beispielsweise ein klassisches User Control, das Sie selbst mit UWP entwickelt haben

An dieser Stelle kommt jetzt ein Vorteil von .NET Core ins Spiel. Sowohl .NET Framework als auch .NET Core unterstützen das erste Szenario, das Hosten eines 1st Party UWP Control aus dem Hause Microsoft. Das zweite Szenario, das Hosten eines benutzerdefinierten UWP Control, wird ebenfalls von .NET Framework und .NET Core unterstützt, falls das UWP Control in C++ geschrieben wurde. Wurde im zweiten Szenario das UWP Control jedoch in C# geschrieben, so lässt es sich nur in .NET-Core-basierten WPF/Windows Forms Apps einsetzen, nicht in .NET-Framework-Applikationen. Somit liegt der Unterschied zwischen .NET Framework und .NET Core bei 3rd-Party-Komponenten. Dies ist nochmals in Tabelle 1 übersichtlich dargestellt. Da .NET Core somit mehr Szenarien als .NET Framework unterstützt, empfiehlt sich als Teil einer Modernisierung von WPF- und Windows-Forms-Applikationen im ersten Schritt eine Portierung auf .NET Core.

Desktop-App-Host 3rd-Party-C#-Komponente 3rd-Party-C++-Komponente
.NET Framework Keine Unterstützung Wird unterstützt
.NET Core Wird unterstützt Wird unterstützt

Tabelle 1: Unterstützung von 3rd-Party-Komponenten

XAML Islands einsetzen

Zum Hosten eines UWP Control in einer WPF-Anwendung ist ein Zugriff auf die XAML Islands API notwendig. Der direkte Zugriff auf diese auch als UWP XAML Hosting API bezeichnete Windows-Schnittstelle ist für C#-Entwickler nicht ganz trivial, da diverse Windows-Runtime-Klassen und COM-Interfaces zum Einsatz kommen. Um das Ganze für WPF- und Windows-Forms-Entwickler einfacher zu gestalten, stellt Microsoft mit dem Windows Community Toolkit</a href> verschiedene .NET Controls zur Verfügung, die den Zugriff auf die XAML Islands API kapseln. Microsoft empfiehlt die Verwendung dieser .NET Controls für WPF und Windows Forms Apps, in denen XAML Islands zum Einsatz kommen sollen, da diese .NET Controls eine deutlich komfortablere Entwicklung ermöglichen als ein direkter Zugriff auf die XAML Islands API.

Die Controls aus dem Toolkit

Mit dem Windows Community Toolkit werden zur Unterstützung von XAML Islands in Form von NuGet-Packages zwei Arten von Controls bereitgestellt:

  • Bereits vordefinierte Controls, die UWP Controls kapseln und die sich sofort in der eigenen Anwendung wie ein natives Control verwenden lassen. Diese Controls werden auch als Wrapped Controls bezeichnet.
  • Host Controls, mit denen sich sowohl UWP Controls von Microsoft als auch eigenentwickelte UWP Controls sehr einfach einbinden lassen.

Falls ein sogenanntes Wrapped Control existiert, ist es zu bevorzugen, da es sich wie ein natives WPF oder Windows Forms Control verwenden lässt. Doch welche Controls werden als Wrapped Controls bereitgestellt?

Wrapped Controls

Microsoft stellt die in Tabelle 2 dargestellten UWP Controls als Wrapped Controls für WPF und Windows Forms zur Verfügung. Voraussetzung ist, dass die Anwendung unter Windows 10 Version 1903 läuft – das ist ja eine allgemeine Voraussetzung für XAML Islands.

Control Beschreibung
InkCanvas und InkToolbar Stellt das Canvas und die Toolbar bereit, um moderne Stifteingaben zu erlauben, die auf der Windows 10 Ink API basieren
MediaPlayerElement Streamt und rendert Medieninhalte
MapControl Stellt eine interaktive Karte dar

Tabelle 2: Die Wrapped Controls

Die in Tabelle 2 dargestellten Controls lassen sich in einer WPF-Anwendung einfach verwenden, indem das NuGet-Package Microsoft.Toolkit.Wpf.UI.Controls hinzugefügt wird. Das ist alles, was notwendig ist. Während in der Vergangenheit noch weitere Schritte nötig waren, um beispielsweise bestimmte Windows 10 APIs in Form von Windows-Metadata-Dateien (.winmd) zu referenzieren, erledigt Microsoft mittlerweile alles Notwendige beim Hinzufügen dieses NuGet-Packages. Dies kann man als sehr entwicklerfreundlich sehen.

Um die Wrapped Controls in einer Windows-Forms-Anwendung einzusetzen, wird das NuGet-Package Microsoft.Toolkit.Forms.UI.Controls hinzugefügt, anschließend ist das Vorgehen identisch zur WPF. Daher fokussieren sich die folgenden Abschnitte lediglich auf WPF-Anwendungen, die Konzepte gelten jedoch auch für Windows Forms.

Das MapControl in WPF einsetzen

Um das UWP MapControl in einer WPF-Anwendung einzusetzen, wird zunächst das NuGet-Package Microsoft.Toolkit.Wpf.UI.Controls hinzugefügt. Dieses NuGet-Package steht ab Version 6 nicht nur für .NET Framework, sondern auch für .NET Core zur Verfügung. Beim Druck dieses Artikels war Version 6 nur in einer Preview-Version vorhanden, somit muss zum Finden des NuGet-Packages in Visual Studio im NuGet Package Manager das Häkchen zum Suchen von Preview-Versionen gesetzt sein, damit Version 6 in der Suche auftaucht.

Nachdem das NuGet-Package installiert wurde, kann es mit den Wrapped Controls auch schon losgehen. Die Controls lassen sich wie gewöhnliche WPF Controls in der WPF-Anwendung einsetzen.

Listing 2 zeigt die MainWindow.xaml-Datei eines brandneuen WPF-Projekts namens WpfAppMapControl. Neben ein paar standardmäßigen Elementen wie Grid, StackPanel und Button ist darin auch das MapControl-Element zu sehen. Das ist jetzt das Wrapper Control für das UWP MapControl. Um es zu verwenden, wurde in Listing 2 das XAML-Namespace-Präfix controls verwendet. Auf dem Window-Wurzelelement wird dieses controls-Präfix dem CLR Namespace Microsoft.Toolkit.Wpf.UI.Controls zugeordnet.

Listing 2: „MainWindow.xaml“

<Window x:Class="WpfAppMapControl.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;
                         assembly=Microsoft.Toolkit.Wpf.UI.Controls"
    FontSize="20" Title="MainWindow" Height="450" Width="800">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="200"/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <StackPanel>
      <Button Content="Go to Frankfurt" Margin="10"
                Click="ButtonFranfkurt_Click"/>
      <Button Content="Go to Redmond" Margin="10"
                Click="ButtonRedmond_Click"/>
    </StackPanel>
    <controls:MapControl x:Name="mapControl"
                       Grid.Column="1"/>
  </Grid>
</Window>

Dem MapControl wird in der MainWindow.xaml-Datei in Listing 2 der Name mapControl gegeben. Über diesen Namen lässt es sich in der Code-behind-Datei referenzieren. Listing 2 enthält auch zwei Buttons, um die Karte des MapControl auf verschiedene Städte zu setzen. Ein Button navigiert nach Frankfurt, ein anderer nach Redmond. Listing 3 zeigt den Event Handler aus der Code-behind-Datei für den Frankfurt-Button. Dabei wird auf dem MapControl die TrySetViewAsync-Methode aufgerufen. Ein Geopoint und der ZoomLevel von 12 werden an diese Methode übergeben. Der Konstruktor der Geopoint-Klasse nimmt ein BasicGeoposition-Objekt entgegen, auf dem in Listing 3 die Properties Latitude und Longitude auf die Koordinaten von Frankfurt gesetzt werden. Um die Klassen Geopoint und BasicGeoposition in C# zu verwenden, ist eine using-Direktive für den Namespace Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT notwendig. Visual Studio unterstützt nach dem Schreiben der Klassen Geopoint und BasicGeoposition mit dem Hinzufügen dieses benötigten Namespace.

Listing 3: „MainWindow.xaml.cs“

private async void ButtonFranfkurt_Click(object sender, RoutedEventArgs e)
  {
    var zoomLevel = 12;
    await mapControl.TrySetViewAsync(
      new Geopoint(
        new BasicGeoposition
        {
          Latitude = 50.110924,
          Longitude = 8.682127
        }
      ),
    zoomLevel);
  }

Wird die Applikation gestartet, tritt beim Initialisieren des MainWindow eine Exception auf. Wird zur innersten Exception dieser äußeren Exception navigiert, stößt man als Entwickler auf folgende Fehlermeldung: „Catastrophic failure – WindowsXamlManager and DesktopWindowXamlSource are supported for apps targeting Windows version 10.0.18226.0 and later. Please check either the application manifest or package manifest and ensure the MaxTestedVersion property is updated.“

Wie bereits erwähnt funktionieren XAML Islands ab Windows Version 1903, was der Build-Nummer 10.0.18226.0 oder höher entspricht. Jetzt muss sichergestellt werden, dass die WPF-Anwendung auch unter dieser Windows-10-Version läuft. Dazu gibt es zwei Möglichkeiten:

  • Eine app.manifest-Datei zum WPF-Projekt hinzufügen, die die Windows-Version festlegt
  • Ein separates Windows Application Packaging Project hinzufügen, das die Windows-Version festlegt und die WPF-Applikation als MSIX-Paket verpackt

In diesem Artikel nutzen wir die app.manifest-Datei, da diese Variante kein zusätzliches Projekt benötigt, und die WPF-Anwendung sich weiterhin über eine klassische .exe-Datei verteilen lässt.

Eine „app.manifest“-Datei konfigurieren

Mit einem Rechtsklick auf das WPF-Projekt lässt sich über Add > New Item ein neues Element hinzufügen. Dabei gibt es, wie in Abbildung 1dargestellt, das Element Application Manifest File.


Abb. 1: Das Application Manifest File im New-Item-Dialog

Als Name wird in Abbildung 1 lediglich app.manifest angegeben und die Datei hinzugefügt. Nach dem Hinzufügen ist die Datei nicht nur Teil des Projekts, sondern auch automatisch in der .csproj-Datei als Manifest-Datei angegeben, was in Listing 4 zu sehen ist.

Listing 4: „WpfAppMapControl.csproj“

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
    <ApplicationManifest>app.manifest</ApplicationManifest>
  </PropertyGroup>
  ...
</Project>

Ein Blick in die app.manifest-Datei zeigt, dass es darin ein compatibility-Element gibt, das, wie in Listing 5 dargestellt, ein application-Element mit verschiedenen auskommentierten Windows-Versionen enthält. Darin lässt sich die OS-ID für Windows 10 aktiv schalten, womit festgelegt ist, dass die WPF-Anwendung nur unter Windows 10 laufen kann.

Listing 5: „app.manifest“

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- A list of the Windows versions that this application has been tested on
    and is designed to work with. Uncomment the appropriate elements
    and Windows will automatically select the most compatible environment. -->
    <!-- Windows Vista -->
    <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->

    <!-- Windows 7 -->
    <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->

    <!-- Windows 8 -->
    <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->

    <!-- Windows 8.1 -->
    <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->

    <!-- Windows 10 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

  </application>
</compatibility>

Mit der app.manifest-Datei und der Angabe von Windows 10 in dieser Datei lässt sich die WPF-Anwendung jetzt ohne Exception starten. In Abbildung 2 ist das UWP MapControl in der WPF-Anwendung zu sehen. Dabei wurde bereits auf den Button Go to Frankfurt geklickt, womit die Karte nach Frankfurt bewegt wurde.


Abb. 2: Das UWP MapControl in WPF

Damit ist das Einbetten eines Wrapped Control abgeschlossen. Das Vorgehen für Windows Forms ist dabei identisch. Auch die app.manifest-Datei lässt sich in Windows Forms wie in einem WPF-Projekt einsetzen, um Windows 10 als Ziel festzulegen.

CalendarView einbetten

Um ein gewöhnliches UWP Control in WPF zu verwenden, das eben nicht als Wrapped Control bereitgestellt wird, kommt die WindowsXamlHost-Klasse zum Einsatz. In diesem Abschnitt wird gezeigt, wie damit die UWP CalendarView aus dem Namespace Windows.UI.Xaml.Controls in WPF eingebunden wird.

Als erster Schritt muss zum WPF-Projekt das NuGet-Package Microsoft.Toolkit.Wpf.UI.XamlHost hinzugefügt werden, da dieses die WindowsXamlHost-Klasse enthält. In einer Windows-Forms-Applikation wird das NuGet-Package namens Microsoft.Toolkit.Forms.UI.XamlHost benötigt.

Zu beachten ist, falls in einer WPF-Anwendung bereits das Package Microsoft.Toolkit.Wpf.UI.Controls mit den Wrapped Controls referenziert wurde, ist das NuGet-Package Microsoft.Toolkit.Wpf.UI.XamlHost automatisch als Abhängigkeit vorhanden. Es muss in einem solchen Fall nicht explizit nochmals referenziert werden.

Nach dem Hinzufügen des NuGet-Packages lässt sich die WindowsXamlHost-Klasse einsetzen, um ein beliebiges UWP UIElement zu laden. Listing 6 zeigt die MainWindow.xaml-Datei eines WPF-Projekts mit einem WindowsXamlHost-Element. Das Element hat das Präfix xamlHost, das auf dem Window-Element dem CLR Namespace Microsoft.Toolkit-Wpf.UI.XamlHost zugeordnet ist. Auf dem WindowsXamlHost-Element ist die InitialTypeName-Property gesetzt, die den voll qualifizierten Klassennamen des zu erstellenden UI-Elements enthält, in diesem Fall Windows.UI.Xaml.Controls.CalendarView. Das WindowsXamlHost-Element hat den Namen windowsXamlHost, damit über diesen Namen in der Code-behind-Datei auf das WindowsXamlHost-Element zugegriffen werden kann. Wird die CalendarView-Instanz erstellt, wird das ChildChanged-Event ausgelöst. In Listing 6 ist für dieses Event ein Event Handler definiert. In diesem Event Handler soll der Code geschrieben werden, damit der ebenfalls in Listing 6 sichtbare WPF TextBlock mit dem Namen txtSelectedDate das selektierte Datum der UWP CalendarView darstellt.

Listing 6: „MainWindow.xaml“

<Window ...
    xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;
                         assembly=Microsoft.Toolkit.Wpf.UI.XamlHost">
  <Grid>
    ...
    <xamlhost:WindowsXamlHost x:Name="windowsXamlHost"
      InitialTypeName="Windows.UI.Xaml.Controls.CalendarView"
      ChildChanged="WindowsXamlHost_ChildChanged"
      Margin="10"/>
    <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10">
      <TextBlock Text="Selected Date: "/>
      <TextBlock x:Name="txtSelectedDate"/>
    </StackPanel>
 </Grid>
</Window>

Listing 7 zeigt den Event Handler für das ChildChanged-Event. Darin wird auf die Child-Property des WindowsXamlHost-Objekts zugegriffen. Ist diese vom Typ CalendarView, wird auf der CalendarView ein Event Handler für das SelectedDatesChanged-Event installiert. In diesem Event Handler wiederum wird die Text-Property des TextBlock mit dem Namen txtSelectedDate auf den Wert des ausgewählten Datums gesetzt.

Listing 7: „MainWindow.xaml.cs“

private void WindowsXamlHost_ChildChanged(object sender, System.EventArgs e)
{
  if (windowsXamlHost.Child
    is Windows.UI.Xaml.Controls.CalendarView calendarView)
  {
    calendarView.SelectedDatesChanged += CalendarView_SelectedDatesChanged;
  }
}

private void CalendarView_SelectedDatesChanged(Windows.UI.Xaml.Controls.CalendarView sender,
  Windows.UI.Xaml.Controls.CalendarViewSelectedDatesChangedEventArgs args)
{
  txtSelectedDate.Text =
    sender.SelectedDates.Count() == 1
    ? sender.SelectedDates.Single().ToString("dd.MM.yyyy")
    : "-";
}

Zum erfolgreichen Ausführen der Anwendung ist wieder eine app.manifest-Datei notwendig, wie es bereits im vorherigen Abschnitt beschrieben wurde. Abbildung 3 zeigt die laufende WPF-Anwendung. Im Hauptteil des Fensters wird die UWP CalendarView dargestellt. Dabei wurde der 24. Dezember ausgewählt. Im unteren Teil des Fensters ist der WPF TextBlock zu sehen, der das ausgewählte Datum ebenfalls anzeigt.


Abb. 3: Die UWP CalendarView in WPF

Auf die in diesem Abschnitt gezeigte Art und Weise lässt sich mit dem WindowsXamlHost-Element jedes in Windows 10 vorhandene UWP Control in einer WPF- oder Windows-Forms-Anwendung einbetten. Doch was ist mit eigenentwickelten UWP Controls?

UWP Controls
werden mehrere UWP Controls eingesetzt, und auf diese viele Event Handler installiert, wird es eventuell mit dem Verwenden von WindowsXamlHost-Elementen etwas unübersichtlich. Dann wären natürlich Wrapped Controls besser. Und natürlich lässt sich auch ein eigenes Wrapped Control für WPF oder Windows Forms schreiben. Dazu muss eine Klasse von WindowsXamlHostBase abgeleitet werden. So ließe sich bspw. eine CalendarViewWrapper-Klasse von WindowsXamlHostBase ableiten, womit sich die UWP CalendarView indirekt über die Wrapper-Klasse einsetzen lässt. Wer in diese Richtung gehen will, schaut am besten die von Microsoft entwickelten Wrapped Controls an, die Open Source sind. Auf GitHub befindet sich der Code, wie beispielsweise jener des MapControl Wrapper.

Eigene UWP Controls hosten

Wie bereits zu Beginn des Artikels erwähnt, ist das Hosten eines eigenen UWP Controls in .NET-Framework-Anwendungen nur dann möglich, wenn das UWP Control in C++ geschrieben wurde. Handelt es sich um ein Managed UWP Control, das in C# geschrieben wurde, lässt es sich nicht in einer .NET-Framework-Anwendung hosten, in einer .NET-Core-Anwendung dagegen schon.

Zum Hosten eines eigenen Control kommt wieder die WindowsXamlHost-Klasse zum Einsatz. Im Folgenden wird ein einfaches, selbstgeschriebenes UWP Control in eine WPF-Anwendung eingebunden. Dazu wird im ersten Schritt eine UWP-Bibliothek mit dem Namen MyUwpControls erstellt, als Minimum-Version wird beim Erstellen Windows 1903 ausgewählt. In der Bibliothek wird das UserControl mit dem Namen SayHelloControl angelegt, das in XAML das in Listing 8 dargestellte StackPanel enthält. Wird in der TextBox des UserControl ein Name eingegeben, wird dieser im TextBlock dank des Data Bindings ausgegeben. Das ist die Funktionalität dieses einfachen Control. Es sagt einfach auf Englisch Hallo, gefolgt von dem eingegebenen Namen.

Listing 8: „SayHelloControl.xaml“

<StackPanel>
  <TextBox x:Name="txt" Header="Firstname" Margin="10"/>
  <StackPanel Orientation="Horizontal" Margin="10">
    <TextBlock Text="Hello" Margin="0 0 5 0"/>
    <TextBlock Text="{x:Bind txt.Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
  </StackPanel>
</StackPanel>

Damit das MyUwpControls-Projekt mit XAML Islands eingesetzt werden kann, muss in der .csproj-Datei noch folgende PropertyGroup hinzugefügt werden:

<PropertyGroup>
  <EnableTypeInfoReflection>false</EnableTypeInfoReflection>
  <EnableXBindDiagnostics>false</EnableXBindDiagnostics>
</PropertyGroup>

Damit ist das MyUwpControls-Projekt mit dem SayHelloControl abgeschlossen und bereit. Im nächsten Schritt wird ein .NET-Core-basiertes WPF-Projekt mit dem Namen WpfXamlIslandsThirdParty angelegt. Nach dem Anlegen werden die folgenden, bereits in diesem Artikel beschriebenen und somit bekannten Schritte ausgeführt:

  • Hinzufügen einer app.manifest-Datei und darin Windows 10 aktivieren
  • Referenzieren des NuGet-Packages Microsoft.Toolkit.Wpf.UI.XamlHost

Im nächsten Schritt wird vom WPF-Projekt eine Referenz auf die UWP-Bibliothek MyUwpControls hinzugefügt. Anschließend wird in der MainWindow.xaml-Datei des WPF-Projekts das in Listing 9 dargestellte User Interface definiert. Wie zu sehen ist, befindet sich darin ein WindowsXamlHost-Element, dessen InitialTypeName-Property den Namen MyUwpControls.SayHelloControl enthält. Neben dem WindowsXamlHost-Element enthält die MainWindow.xaml-Datei noch einen WPF TextBlock mit etwas Text.

Listing 9: „MainWindow.xaml“

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition Height="Auto"/>
  </Grid.RowDefinitions>
  <xamlhost:WindowsXamlHost InitialTypeName="MyUwpControls.SayHelloControl"/>
  <TextBlock Grid.Row="1" Margin="10" FontSize="20">
    Im oberen Teil ist das UWP Control, bestehend aus TextBox und TextBlöcken
  </TextBlock>
</Grid>

Die Solution kompiliert, doch beim Starten der Anwendung kommt es in SayHelloControl zur in Abbildung 4 dargestellten Exception, die lediglich sagt: „XAML parsing failed“. Bei einem genauen Blick fällt auf, dass es sich natürlich um eine UWP XamlParseException handelt, die aus dem Namespace Windows.UI.Xaml.Markup stammt.


Abb. 4: XamlParseException in „SayHelloControl“

Das in C# geschriebene UWP Control benötigt ein UWP-XamlApplication-Objekt, und beim Einsatz mit XAML Islands muss dieses Objekt explizit bereitgestellt werden. Dazu muss ein drittes Projekt zur Solution hinzugefügt werden, nämlich eine leere UWP-Anwendung. Diese wird hier einfach UwpApplication genannt, und als Minimum-Version wird beim Erstellen Windows 10 1903 angegeben. Die Solution sieht somit wie in Abbildung 5 dargestellt aus, bestehend aus drei Projekten.


Abb. 5: Die Solution in Visual Studio

Jetzt wird im UwpApplication-Projekt eine Referenz auf das MyUwpControls-Projekt hinzugefügt. Anschließend wird im UwpApplication-Projekt das NuGet-Package Microsoft.Toolkit.Win32.UI.XamlApplication installiert, das Helferklassen für XAML-Islands-Szenarien enthält. Jetzt wird die App.xaml-Datei des UwpApplication-Projekts geöffnet und darin als Wurzelelement anstatt eines Application-Elements ein XamlApplication-Element definiert. Visual Studio schlägt dabei für das XamlApplication-Element den Namespace Microsoft.Toolkit.Win32.UI.XamlHost vor, was zum Resultat in Listing 10 führt. In Listing 10 hat das XamlApplication-Element das Präfix xamlHost, das genau auf jenen CLR Namespace zeigt.

Listing 10: „App.xaml“

<xamlhost:XamlApplication
  xmlns:xamlhost="using:Microsoft.Toolkit.Win32.UI.XamlHost"
  x:Class="UwpApplication.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:UwpApplication">
</xamlhost:XamlApplication>

Im nächsten Schritt geht es in der Code-behind-Datei App.xaml.cs des UwpApplication-Projekts weiter. Dort wird sämtlicher Code aus der App-Klasse entfernt, die App-Klasse von XamlApplication abgeleitet und im Konstruktor die Initialize-Methode aufgerufen. Eventuell benötigt Visual Studio den ein oder anderen Rebuild, damit die Initalize-Methode via IntelliSense erscheint.

Listing 11: „App.xaml.cs“

sealed partial class App: Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
{
  public App()
  {
    this.Initialize();
  }
}

Im letzten Schritt wird das UwpApplication-Projekt von der WPF-Anwendung der Solution referenziert, die den Namen WpfXamlIslandsThirdParty trägt. Jetzt ist es an der Zeit, die komplette Solution zu bauen. Üblicherweise schlägt an dieser Stelle der Build fehl, da die WPF-Anwendung mit AnyCPU anstatt mit x64 oder x32 kompiliert wird. Das ist aus den Warnungen im Error-Fenster von Visual Studio ersichtlich. Hier hilft der Configuration Manager von Visual Studio weiter. Abbildung 6 zeigt die angepasste Konfiguration im Configuration Manager, bei der alle Projekte mit x86-Ziel kompiliert werden. Nach dem Speichern der Konfiguration klappt es dann auch mit dem Kompilieren der Solution.


Abb. 6: Im Configuration Manager x86 einstellen

Jetzt steigt die Spannung beim Starten der Anwendung. Die Anwendung startet erfolgreich, und wie erwartet und wie in Abbildung 7 zu sehen, wird im oberen Teil der WPF-Anwendung das UWP Control SayHelloControl bestehend aus TextBox und TextBlock zum Hallo sagen angezeigt.


Abb. 7: Im oberen Teil mit dunklem Hintergrund ist das UWP Control

Somit hat das Einbinden eines eigenen UWP Controls in einer .NET-Core-basierten WPF-Anwendung mit XAML Islands funktioniert. Wie auch bei Standard UWP Controls, für die es kein Wrapped Control gibt, wird die WindowsXamlHost-Klasse eingesetzt.

Von UWP zu WinUI 3.0

Die in diesem Artikel beschriebene Version von XAML Islands ist XAML Islands 1.0, die ab Windows 10 Version 1903 verfügbar ist. Doch in Zukunft werden XAML Islands auch auf älteren Windows-10-Versionen funktionieren. Doch wie das? Um das zu verstehen, muss man auf die Entwicklung der Universal Windows Platform (UWP) schauen.

Microsoft hat schon vor ein paar Jahren festgestellt, dass die Universal Windows Platform das Problem hat, dass Entwickler neue Controls immer nur dann verwenden können, wenn ein Benutzer auch die neue Windows-10-Version installiert hat. Um dieses Problem zu lösen, hat Microsoft verschiedenste UWP Controls von Windows 10 entkoppelt und in Form von NuGet-Packages ausgeliefert. Somit lassen sich neueste Controls auch unter älteren Windows-10-Versionen verwenden. Diese NuGet-Packages sind unter dem Namen WinUI bekannt, und jenes mit den UWP Controls trägt den Namen Microsoft.UI.Xaml.

Die UWP Controls als solches von Windows 10 zu entkoppeln war nur der erste Schritt.
Im nächsten Schritt plant Microsoft, nicht nur UWP Controls via NuGet-Packages bereitzustellen, sondern auch die ganze UWP XAML Runtime und auch die Rendering Pipeline. Also alles, was zu einem kompletten UI Framework gehört. Dieses UI Framework beabsichtigt Microsoft für das Jahr 2020 in der finalen Version zu veröffentlichen. Es trägt den Namen WinUI 3.0 und wird Open Source sein, das Repository gibt es bereits heute auf GitHub.

Microsoft plant in Visual Studio eine neue Projektvorlage namens „Windows Application“, mit der ein WinUI-3.0-Projekt erstellt wird. Das Besondere an WinUI 3.0 ist, dass es sowohl das UWP-Modell mit der Sandbox als auch das klassische Win32-Modell mit gewöhnlicher .exe-Datei unterstützt. Somit ist die aus UWP bekannte Sandbox für WinUI-3.0-Applikationen optional. Diese Sandbox war bei einer Entscheidung zwischen UWP und WPF oft der Grund, warum Unternehmen am Ende WPF genommen haben, da es eine klassische .exe-Datei gibt. Doch mit WinUI 3.0 lässt sich ebenso eine klassische .exe-Datei erstellen, und zudem lassen sich die ganzen modernen UWP Controls und XAML-Features wie kompilierte Data Bindings direkt in einer WinUI-3.0-Anwendung nutzen. WinUI 3.0 ist somit das Beste aus UWP und WPF.

Es sieht also sehr stark danach aus, dass ab 2020 die WinUI 3.0 das moderne und empfohlene UI Framework für Windows-Desktopapplikationen sein wird. Und mit WinUI 3.0 wird Microsoft auch eine neue Variante von XAML Islands einführen, mit der sich WinUI 3.0 Controls problemlos in WPF- und Windows-Forms-Anwendungen integrieren lassen. Und da WinUI 3.0 von Windows 10 entkoppelt ist, wird die nächste Version von XAML Islands auch auf Windows-10-Versionen funktionieren, die älter als Version 1903 sind.

Fazit

Dieser Artikel hat die Grundlagen zu XAML Islands gezeigt. Mit XAML Islands lassen sich WPF- und Windows-Forms-Anwendungen mit UWP Controls modernisieren. .NET Core unterstützt dabei alle Szenarien. Es lassen sich sowohl UWP Controls von Microsoft als auch eigene, in C++, aber auch in C# geschriebene UWP Controls in der eigenen WPF- oder Windows-Forms-Applikation hosten.

XAML Islands erlaubt es, bestehende Applikationen mit Windows-10-Features aufzufrischen, ohne dass diese Anwendungen dafür neu geschrieben werden müssen. Microsoft ist zudem dabei, den kompletten UWP-XAML-Stack von Windows 10 zu entkoppeln und als eigenes, XAML-basiertes UI Framework unter dem Namen WinUI 3.0 bereitzustellen. WinUI 3.0 Controls sind moderne UWP Controls, die sich weiterhin via XAML Islands in WPF und Windows Forms Apps einbinden lassen werden. Für brandneue Windows-Desktopapplikationen stellt WinUI 3.0 wohl ab dem Release im Jahr 2020 die modernste und beste Wahl dar. Microsoft ist und bleibt also auch im Windows-Desktopumfeld innovativ und aktiv, und als Entwickler können wir gespannt und freudig auf die Zukunft mit WinUI 3.0 blicken, und uns freuen, dass bestehende .NET-Applikationen dank XAML Islands auch von dieser Zukunft profitieren können.

 

Ihr aktueller Zugang zur .NET- und Microsoft-Welt.
Der BASTA! Newsletter:

Behind the Tracks

.NET Framework & C#
Visual Studio, TFS, C# & mehr

Agile & DevOps
Best Practices & mehr

Web Development
Alle Wege führen ins Web

Data Access & Storage
Alles rund um´s Thema Data

HTML5 & JavaScript
Leichtegewichtig entwickeln

User Interface
Alles rund um UI- und UX-Aspekte

Microservices & APIs
Services, die sich über APIs via REST und JavaScript nutzen lassen

Security
Tools und Methoden für sicherere Applikationen

Cloud, Azure, Serverless
Cloud-basierte & Native Apps