Cross-Device-Entwicklung mit Universal Windows Apps

Universal Apps für alle Windows-Geräte
Kommentare

Am 2. April 2014 fand wieder einmal Microsofts jährliche Entwicklerkonferenz Build statt, auf der passend zu Windows 8.1 auch Windows Phone 8.1 vorgestellt wurde. Das Besondere an der neuen Phone-Version ist, dass die Funktionen der Windows Phone Runtime beinahe identisch mit denen der Windows Runtime sind. Das schafft eine hohe Codekompatibilität. Eine neue zusätzliche Visual-Studio-Projektvorlage namens Universal Windows Apps ermöglicht dementsprechend das einfache Bereitstellen einer Codebasis und unterstützt die Nutzung gerätespezifischer Funktionen.

Ich erinnere mich noch genau an das Windows-Developer-Sonderheft zum Thema Windows 8, das im September 2012 erschien. Zu diesem Zeitpunkt existierte noch nicht einmal eine finale Version von Windows 8 und Windows-Store-Apps hießen noch Metro-Style-Apps. Bestandteil dieser Sonderausgabe war auch ein Interview mit mir zu meiner Windows-8-Entwickler-Roadshow und meinen Prognosen für Windows 8. Damals sagte ich: „Windows 8 läuft auf allen gängigen Geräten und somit auch die neue Windows Runtime (kurz WinRT). Das ermöglicht, dass Metro-Style-Apps auf allen gängigen Geräten bereitgestellt werden können. So beginnt man im Büro mit der Analyse von Geschäftsdaten auf dem PC, beim Meeting wird einfach ein Slate mitgenommen, das die gleichen Daten und die gleiche App bietet und auf dem Weg zum Zug wird dann das handliche Windows Phone herausgeholt, das ebenfalls dieselben Informationen zur Verfügung stellt. Eine zusätzliche Zwischenkontrolle ist auch von Zuhause aus über den Fernseher (Xbox) möglich. Also kurz ausgesprochen: Das Cross-Device-Konzept für eigene Apps“.

Am 26. Oktober 2012 wurde dann die finale Windows-8-Version veröffentlicht. Dass die dort enthaltenen „Metro-Style-Apps“ nicht auf dem Windows Phone 7 laufen würden, wussten wir bereits durch die neue Windows Runtime. Als drei Tage später Windows Phone 8 veröffentlicht wurde, war die Enttäuschung bei Vielen allerdings groß: Windows Phone 8 hatte zwar den Kern von Windows 8 RT bekommen, doch die Codekompatibilität zu Windows-Store-Apps ließ stark zu wünschen übrig. Das lag daran, dass hier nicht direkt die Windows Runtime für Apps zum Einsatz kommt, sondern eine Kombination aus der neuen Windows Phone Runtime und Silverlight for Windows Phone. Bei einigen Punkten mag das durch die technischen Unterschiede verständlich sein. Eher unwohl wurde den Entwicklern aber dann, wenn sie gleiche Funktionen verwenden wollten und diese nicht einheitlich angesprochen werden konnten. Ein Beispiel dafür wäre der Zugriff auf die Live-Tiles-Schnittstelle zur Versorgung der Kacheln mit neuen Informationen.

Der hohe Aufwand bei der Pflege beider Welten zeigt sich auch im Store: Nur sehr wenige Apps sind für beide Geräte erhältlich. Wo war also die schöne bunte Welt, die man sich erhofft hatte? Dem großen Cross-Device-Traum stand ein großes Hindernis im Weg.

Cross-Device-Entwicklung: Bisherige Lösungsansätze

Abseits der neuen bunten Welt war die Anzahl der .NET-Framework-Varianten stark gestiegen: .NET Compact Framework, Silverlight, Silverlight for Windows Phone, .NET Framework Client Profile, Windows Runtime, Windows Phone Runtime usw. Sie haben alle etwas gemeinsam: Sie sind nur eine Teilmenge des ursprünglichen .NET Frameworks und die erzeugten Klassenbibliotheken sind untereinander nicht kompatibel. Für einige Entwickler stellte dies ein großes Problem dar.

Microsoft ließ die Entwickler aber nicht im Stich und stellte einige Lösungsansätze für eine gemeinsame Codebasis zur Verfügung. Schon seit Visual Studio 2005 gibt es das „Linked Files“-Feature, mit dem etwa Klassendateien projekttypunabhängig wiederverwendet werden können. Die einzige Bedingung war, dass diese Klassendateien keine Abhängigkeiten zum Projekttyp aufweisen. Da das eine große Herausforderung darstellt, kam das Feature häufig nur für Entities (Datenklassen) zum Einsatz.

Die Portable Class Library

Nur das Teilen von Datenklassen kann keine dauerhafte Lösung sein. Viel wichtiger ist es, Logik wiederverwenden zu können. Deshalb löste die ab Visual Studio 2012 hinzugekommene Portable Class Library einen regelrechten Jubel unter den Entwicklern aus. Hierbei handelt es sich um eine spezielle Klassenbibliothek, die für unterschiedliche .NET-Profile gültig ist. Das ermöglicht eine gemeinsame Codebasis für unterschiedliche Projekttypen.

Es gibt allerdings ein Problem: Die festgelegten .NET-Profile können nicht komplett ausgereizt werden, da die Portable Class Library nur den Zugriff auf Funktionen ermöglicht, die in allen festgelegten Profilen vorhanden sind – der kleinste gemeinsame Nenner sozusagen.

Somit musste ein Workaround her. Bei der Portable Class Library ist dieser das Bereitstellen von Events. Die .NET-Profile abonnieren diese Events und können dann die plattformspezifischen Funktionen ausführen. Schade ist nur, dass dennoch oft die gleiche Logik wiederverwendet wird und sich lediglich die Methodenaufrufe unterscheiden. Das macht den Umweg über Events aus Architektursicht nicht gerade angenehm und schön.

Universal Windows Apps

Seit dem Visual Studio 2013 Update 2 RC gibt es eine neue Projektvorlage mit dem Namen „Universal Windows Apps“. Sie ermöglicht, Windows-8.1-Store-Apps und Windows-Phone-8.1-Apps mit einer gemeinsamen Codebasis zu entwickeln. Anders als der Name vermuten lässt, dient diese Projektvorlage jedoch nicht als eigenständige Applikation. Beim Erzeugen eines neuen Projekts wird zusätzlich ein Windows-8.1-Store- und ein Windows-Phone-8.1-Projekt angelegt. Das Universal-Windows-App-Projekt selbst ist in den anderen beiden Projekten automatisch referenziert. Es beinhaltet eine App.xaml, die nun für beide Projekte automatisch geteilt wird. Das ermöglicht das Festlegen von Ressourcen, die für beide Welten zugänglich sind.

Auf den ersten Blick ähnelt das Ganze stark einer Portable Class Library. Und das stimmt auch, obgleich es einige Unterschiede gibt: Zuerst einmal werden nur Windows-Store- und Windows-Phone-Projekte mit dem Update 8.1 unterstützt, der Support für die Xbox One soll allerdings bald folgen. Außerdem stehen hier alle plattformspezifischen Funktionen beider Welten zur Verfügung. Aus Architektursicht ein Hochgenuss.

Die erste eigene Universal Windows App

Am besten sehen wir uns das gemeinsam mit einem kleinen Beispiel an. Sie benötigen dazu das aktuelle Windows-8.1-Update und Visual Studio 2013 Update 2 RC. In Visual Studio klicken wir auf „Neues Projekt“ und nach der Entwicklungssprache Visual C# wird die neue Projektvorlage „Windows Universal Apps“ ausgewählt. Passend dazu wird die erste Projektvorlage „Leere App“ ausgewählt und anschließend ein Projekt mit dem Namen „WindowsDeveloperApp“ angelegt.

Nun wurden drei Projekte erzeugt, jeweils mit der Endung .Shared (Abb. 1). Hier sollen dann alle gemeinsam genutzten Daten hinzugefügt werden, wie UserControls, DataTemplates, ValueConverter, Bilder (Assets) oder wie zu Beginn als Standard eine allgemeine App.xaml.

Abb. 1: Die Projekte der neuen Universal-Windows-App-Vorlage

Der erste Schritt wäre jetzt das Schmücken der beiden App-Oberflächen. Für die Apps genügen ein einfacher Titel und eine Liste der neuesten Windows-Developer-News. Zum Anzeigen des Titels reicht ein TextBlock-Steuerelement und für die Newsliste das ListView-Steuerelement. Diese Steuerelemente sind in Visual Studio wie gewohnt im Werkzeugkasten auf der linken Seite zu finden. Durch Drag and Drop platzieren wir sie im Designer auf der App-Oberfläche. Normal könne dieser Part auch als eigenes UserControl innerhalb des Shared-Projekts erzeugt werden, allerdings erhält der Titel einen anderen Style für die Titelgröße. Schließlich soll das Beispiel demonstrieren, wie mit plattformspezifischen Eigenheiten umgegangen wird und in den meisten Fällen unterscheidet sich das UI auf einem Windows Phone eben von dem einer Windows-Store-App. Den XAML-Code der Windows-Store-App finden Sie in Listing 1, den der Windows-Phone-App in Listing 2.

<grid background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <textblock horizontalalignment="Left" margin="24,24,0,0" textwrapping="Wrap" text="Windows Developer App" verticalalignment="Top" style="{StaticResource HeaderTextBlockStyle}">
  <listview margin="24,99,10,10">
</listview></textblock></grid>

 

<grid>
  <textblock horizontalalignment="Left" margin="10,10,0,0" textwrapping="Wrap" text="Windows Developer App" verticalalignment="Top" style="{StaticResource SubheaderTextBlockStyle}">
  <listview margin="10,54,10,10">
</listview></textblock></grid>

Nun muss beim Laden der App eine Datenabfrage aus dem Windows-Developer-RSS-Feed erfolgen. Dafür genügt ein leichtgewichtiger Proxy, der von der SyndicationClient-Klasse bereitgestellt wird. Diesen kapseln wir jetzt beim Shared-Projekt und erzeugen dazu eine neue Klasse mit dem Namen RSSProxy (Listing 3).

Die Antwort des Servers erfolgt mit XML. Die SyndicationClient-Klasse erstellt daraus automatisch C#-Objekte, was uns ein mühevolles manuelles Auswerten der XML-Daten erspart. Diese C#-Objekte müssen anschließend den ListViews hinzugefügt werden.

namespace WindowsDeveloperApp
{
  public class RSSProxy
  {
    public async Task<ilist<syndicationitem>> LoadNews()
    {
      var syndicationClient = new SyndicationClient();
      var feeds = await syndicationClient.RetrieveFeedAsync(new Uri("ht tp://windowsdeveloper.de/news.xml"));
       
      return feeds.Items;
    }
  }
}
</ilist<syndicationitem>

Für den nächsten Schritt bauen wir ein ViewModel, das ebenfalls geshared wird. Normalerweise ist ein ViewModel stark abhängig von einer spezifischen View und sollte als deren Abbild nicht wiederverwendet werden. Wenn Elemente auf einer Oberfläche nicht vorhanden sind, aber dennoch in einem ViewModel existieren, wird die Komplexität unnötig gesteigert. In unseren Fall bilden aber beide Viewoberflächen exakt den gleichen Inhalt ab und eine Wiederverwendung ist dementsprechend legitim. Wir legen also im Shared-Projekt einen neuen Ordner mit dem Namen „ViewModels“ an und erzeugen darin die Klasse MainPageViewModel. Diese erhält eine ObservableCollection Property für die News. Darauf folgt eine Methode, die auf unseren erzeugten RSSProxy zugreift und die News-Property füllt – die Implementierung ist in Listing 4 zu sehen.

namespace WindowsDeveloperApp.ViewModels
{
  public class MainPageViewModel
  {
    public ObservableCollection News { get; set; }

    public MainPageViewModel()
    {
      News = new ObservableCollection();
    }

    public async void LoadNews()
    {
      RSSProxy rssProxy = new RSSProxy();
      var news = await rssProxy.LoadNews();

      foreach (SyndicationItem syndicationItem in news)
      {
News.Add(syndicationItem);
      }
    }
  }
}
</syndicationitem> </syndicationitem>

Die ViewModels müssen nun in beiden App-Projekten innerhalb der MainPage im Ressourcenbereich instanziiert werden und ein DataBinding muss auf die ItemsSource Property folgen. Den XAML-Code für das Windows-App-Projekt finden Sie in Listing 5, den für das Windows-Phone-Projekt in Listing 6.

<page x:class="WindowsDeveloperApp.MainPage" xmlns="ht tp://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="ht tp://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WindowsDeveloperApp" xmlns:d="ht tp://schemas.microsoft.com/expression/blend/2008" xmlns:mc="ht tp://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodels="using:WindowsDeveloperApp.ViewModels" mc:ignorable="d">
  <page.resources>
    <viewmodels:mainpageviewmodel x:key="MainPageViewModel">
  </viewmodels:mainpageviewmodel></page.resources>
 
  <grid datacontext="{StaticResource MainPageViewModel}" background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <textblock horizontalalignment="Left" margin="24,24,0,0" textwrapping="Wrap" text="Windows Developer App" verticalalignment="Top" style="{StaticResource HeaderTextBlockStyle}">
    <listview itemssource="{Binding News}" margin="24,99,10,10">
  </listview></textblock></grid>
</page>

 

<page x:class="WindowsDeveloperApp.MainPage" xmlns="ht tp://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="ht tp://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WindowsDeveloperApp" xmlns:d="ht tp://schemas.microsoft.com/expression/blend/2008" xmlns:mc="ht tp://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodels="using:WindowsDeveloperApp.ViewModels" mc:ignorable="d" background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <page.resources>
    <viewmodels:mainpageviewmodel x:key="MainPageViewModel">
  </viewmodels:mainpageviewmodel></page.resources>
   
  <grid datacontext="{StaticResource MainPageViewModel}">
    <textblock horizontalalignment="Left" margin="10,10,0,0" textwrapping="Wrap" text="Windows Developer App" verticalalignment="Top" style="{StaticResource SubheaderTextBlockStyle}">
    <listview itemssource="{Binding News}" margin="10,54,10,10">
  </listview></textblock></grid>
</page>

Für die Darstellung der einzelnen Listeneinträge wird nun ein DataTemplate benötigt, das an erster Stelle den Titel und direkt darunter die Beschreibung zeigen soll. Der Titel selbst soll in einer fetten Schriftart angezeigt werden. Damit beide Projekte von diesen Einstellungen Gebrauch machen können, erfolgen sie im Shared-Projekt innerhalb der App.xaml-Datei im Ressourcenbereich (Listing 7). Den beiden ListView-Steuerelementen kann man anschließend das DataTemplate ganz einfach via Binding zuweisen. Listing 8 zeigt den Code der ListView aus dem Windows-Store-Projekt, gleichermaßen kann das Binding auch beim Windows-Phone-Projekt gesetzt werden.

<application x:class="WindowsDeveloperApp.App" xmlns="ht tp://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="ht tp://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WindowsDeveloperApp">
 
  <application.resources>
    <datatemplate x:key="NewsDataTemplate">
      <stackpanel>
        <textblock text="{Binding Title.Text}" fontweight="Bold"></textblock>
        <textblock text="{Binding Summary.Text}"></textblock>
      </stackpanel>
    </datatemplate>
  </application.resources>
</application>

 

<listview itemtemplate="{StaticResource NewsDataTemplate}" itemssource="{Binding News}" margin="24,99,10,10">
</listview>

Die beiden Oberflächen wären nun mit einem gemeinsamen DataTemplate fertig. Passend dazu haben wir ein gemeinsames ViewModel erzeugt und das DataBinding gesetzt. Die Logik für die RSS-Feed-Abfrage wurde auch schon implementiert. Jetzt muss diese Abfrage beim Aufruf der jeweiligen App vom ViewModel gestartet werden. In der Regel würde man das über die Expression Blend Behaviors lösen, damit dieses Beispiel leichtgewichtig bleibt, erfolgt bei beiden Projekten jedoch ein kleiner Codeaufruf aus der Code-Behind-Datei.

Von MainPageViewModel wird ein Feld angelegt. Innerhalb des MainPage-Konstruktors wird die Instanz aus den Ressourcen gezogen und die Methode LoadFeeds ausgeführt. Dieser Schritt erfolgt bei beiden Projekten. Listing 9 zeigt, wie es im Windows-Phone-Projekt aussieht.

private MainPageViewModel _mainPageViewModel;

public MainPage()
{
  this.InitializeComponent();

  this.NavigationCacheMode = NavigationCacheMode.Required;

  mainPageViewModel = (MainPageViewModel) Resources["MainPageViewModel"];
  mainPageViewModel.LoadNews();
}

Die wichtigsten Punkte wurden nun alle erfolgreich umgesetzt. Zum Testen der beiden Apps kann man im Menü über den Debug-Button (lokaler Computer) das Kontextmenü öffnen und ganz unten das gewünschte Startprojekt auswählen. Anschließend lässt sich die App wie gewohnt mit F5 ausführen und testen.

Bis zu dieser Stelle weist unser Beispiel große Ähnlichkeiten zu einer Portable-Class-Library-Lösung auf. Aktuell werden aber sehr viele Funktionalitäten unterstützt, die vor den 8.1-Updates noch nicht existierten. Die Einschränkungen sind kaum noch bemerkbar, wobei es mit einer gemeinsamen App.xaml mit DataTemplate beginnt und bis zur gemeinsamen Logik der RSS-Feed-Abfrage reicht. Ein weiterer großer Vorteil gegenüber der Portable Class Library soll aber noch gezeigt werden: das Ausführen von plattformspezifischen Funktionen.

Eine solche wäre etwa das SettingsPane, das es nur für Windows-Store-Apps gibt und das normalerweise von der App.xaml.cs-Datei aus gesetzt wird. Ausgeführt wird es beim Öffnen der Einstellungen über die Charms-Bar. In diesem Element können unter anderem die Einstellungen oder weitere Informationen zur App hinterlegt werden. Unter Windows Phone existiert diese Funktion allerdings nicht. Fügt man beispielsweise in der App.xaml.cs innerhalb der ersten Zeile der OnLaunched-Methode den Code aus Listing 10 hinzu, erhalten wir beim Builden des Projekts eine Exception. Die Fehlermeldung des Windows-Phone-Projekts weist uns darauf hin, dass die gewünschte Klasse nicht kompatibel und dementsprechend auch nicht vorhanden ist (Abb. 2).

Abb. 2: Fehler beim Builden

Die Universal Windows App erlaubt uns dennoch, diesen Code zu verwenden. Dazu wird einfach eine #IF-Kondition zur gewünschten Plattform festgelegt, sodass der Code für das Windows-Phone-Projekt beim Builden automatisch und bewusst ausgelassen wird. In den Projekteigenschaften sehen wir die neuen Kompilierungssymbole WINDOWS_APP und WINDOWS_PHONE_APP. Nachdem wir diese hinzugefügt haben, lässt sich das Projekt wieder erfolgreich builden (Listing 11).

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
  Windows.UI.ApplicationSettings.SettingsPane settingsPane = Windows.UI.ApplicationSettings.SettingsPane.GetForCurrentView();
...
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if WINDOWS_APP
  Windows.UI.ApplicationSettings.SettingsPane settingsPane = Windows.UI.ApplicationSettings.SettingsPane.GetForCurrentView();
#endif
...

Nun sind alle Schritte zur Erstellung Ihrer eigenen Universal Windows App vollbracht. Abbildungen 3 und 4 zeigen, wie solch eine App aussehen kann.

Abb. 3: Die fertige Windows-Developer-App für Windows Phone

Abb. 4: Die fertige Windows-Developer-App für Windows 8

Bestehende Projekte als Universal Windows Apps

Bestehende Projekte lassen sich ganz einfach in Universal Windows Apps übertragen. Dazu muss das aktuelle Projekt in die neue Projektvorlage 8.1 konvertiert werden. Das funktioniert in Visual Studio über die Projekteigenschaften: Klicken Sie unter „Library“ auf den „Change“-Button. Wählen Sie anschließend nach einem Rechtsklick auf das Projekt im Kontextmenü des Projektmappenexplorers den Menüpunkt „Windows Phone 8.1 hinzufügen…“ oder „Windows Store App 8.1 hinzufügen…“ aus. Ein neues Projekt wird automatisch hinzugefügt, genauso wie eine Universal-Windows-App, die dann ebenfalls automatisch bei beiden Projekten referenziert ist.

Universal Windows Apps vs. Portable Class Library

Doch wann verwendet man eigentlich eine Universal Windows App und wann eine Portable Class Library? Eine Universal Windows App sollte immer dann zum Einsatz kommen, wenn man projektspezifischen Code schreibt. Möchte man Code wiederum für unterschiedliche Projekte zur Verfügung stellen, ist dies am besten durch die Portable Class Library zu lösen.

Fazit

Endlich wurde das große Cross-Device-Prinzip verwirklicht und meine anfänglich erwähnten Prognosen sind zumindest teilweise eingetroffen. Dank des Shared-Konzepts können neue oder bestehende Projekte ohne großen Aufwand für alle Welten zugänglich gemacht werden. Dass jedes Gerät dennoch sein eigenes App-Projekt benötigt, ist aufgrund der gerätespezifischen Eigenheiten absolut verständlich. Ein Beispiel hierfür wäre die fehlende Druckfunktion unter Windows Phone.

Microsoft ist sowohl aus Entwickler- als auch aus Verbrauchersicht auf dem richtigen Weg. Beim Kauf eines neuen Geräts entscheidet sich ein Verbraucher meist für eines mit dem Betriebssystem, auf dem er seine bereits erworbenen Inhalte weiter verwenden kann. Auch die gewohnte Benutzung ist ein wichtiges Argument. Dies beweist am besten die Android-Welt, die die Verbraucher durch sehr günstige Geräte abholt und dafür sorgt, dass spätere Kaufentscheidungen genau aus den genannten Gründen gefällt werden. Aus Entwicklersicht wiederum ist es sowieso schon sehr schwer, die „richtige“ Plattform zu treffen und dabei nicht zu viel Zeit zu investieren. Damit kommt das Konzept der Universal Windows Apps genau richtig.

Ich persönlich bin jedenfalls schon richtig gespannt, was sich wohl hinter Windows 9 verbirgt. Schafft es Microsoft, die Welten noch enger zusammenzubringen? Wird die Sprachassistentin Cortana auch den Weg auf Windows finden? Bekommt mein Windows-Tablet dann auch eine Action-Bar? Wird aus den Universal Windows Apps mehr als nur eine Shared-Projektvorlage? Es bleibt auf jeden Fall spannend. Fakt ist aber schon heute: Die native App-Entwicklung macht auf keiner Plattform so viel Spaß wie mit der Windows Runtime, und das neue Universal-Windows-App-Konzept steigert das noch einmal. Ich wünsche Ihnen viel Spaß beim Programmieren für alle Windows-Geräte mit nur wenig Aufwand!

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -