Simple Recipes – User Interface und Plattformanpassungen
Moderne UI-Gestaltung mit der Uno Platform, Teil 5
Wir setzen unseren Workshop fort und erstellen die Beispiel-App zur Rezepteverwaltung mit der Uno Platform. Es geht um den Aufbau der Benutzeroberfläche, um die Navigation zwischen Haupt- und Detailseite und mögliche Anpassungen an Plattformbesonderheiten. Final können wir die App dann auf unterschiedlichen Gerätetypen wie Desktop, Browser und Mobile testen.
Jetzt erhält Simple Recipes ein Gesicht: die Rezepteübersicht. Sie ist der zentrale Einstieg in die App und eignet sich hervorragend, um das Zusammenspiel von Uno-XAML und MVVM zu demonstrieren. Im Layout wird zunächst eine zweigeteilte Struktur verwendet: Links befindet sich eine schmale Sidebar, die als einfache Navigation mit Buttons für „Home“ und „Favorites“ dient. Rechts liegt der eigentliche Inhaltsbereich, in dem eine Überschrift („Recipe List“) und darunter die Liste der Rezepte angezeigt werden. Funktional soll die Seite alle verfügbaren Rezepte anzeigen, eine Auswahl ermöglichen und optisch so gestaltet sein, dass sie sowohl auf dem Desktop als auch auf mobilen Geräten überzeugt. Als Basiskomponente verwenden wir eine ListView, die über x:Bind an die Recipes-Eigenschaft des ViewModel gebunden ist. Jedes Element wird über ein DataTemplate dargestellt, in dem ein Border mit abgerundeten Ecken das Layout für Bild und Text kapselt. Darin sorgt ein zweispaltiges Grid mit Bild links und Titel/Beschreibung rechts für eine klare, gut lesbare Struktur – mit relativ wenig XAML-Aufwand.
Um zwischen Kachel-Layout auf dem Desktop und einspaltiger Liste auf mobilen Geräten zu wechseln, nutzen wir den Visual State Manager direkt im Inhaltsbereich. Ab einer gewissen Breite – beispielsweise 800 Pixel – wird die ListView so konfiguriert, dass ein WrapPanel als ItemsPanelTemplate verwendet wird und mehrere Rezeptkarten nebeneinander platziert werden. Darunter fällt die Ansicht automatisch auf einspaltige Darstellung zurück, indem ein einfaches StackPanel als ItemsPanel gesetzt wird. Dadurch kann dieselbe Seite sowohl auf einem großen Monitor als auch auf dem Smartphone genutzt werden, ohne dass Layout-Code dupliziert werden muss. Farben, Schriften und Abstände orientieren sich an den bestehenden App- beziehungsweise System-Theme-Ressourcen; der Border mit CornerRadius übernimmt diese Vorgaben und rundet die Darstellung optisch ab.
Listing 1 zeigt einen Auszug aus der Hauptseite zur Steuerung des Layouts auf unterschiedlichen Devices.
Listing 1: Steuerung des Layouts in der Hauptseite (Auszug)
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="AdaptiveStates">
<!-- Mobile / schmale Ansicht: einspaltige Liste -->
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="RecipeList.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</VisualState.Setters>
</VisualState>
<!-- Desktop / breite Ansicht: Kachel-Layout -->
<VisualState x:Name="Wide">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="800" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="RecipeList.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel
ItemHeight="220"
ItemWidth="260"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Die Anbindung an das ViewModel folgt konsequent dem MVVM-Ansatz: Der DataContext der Seite verweist auf ein RecipeListViewModel, das die Rezepte lädt und als ObservableCollection<Recipe> unter viewModel.Recipes zur Verfügung stellt. Die Auswahl eines Elements und der Klick auf ein Listenelement werden über IsItemClickEnabled und den ItemClick-Event verarbeitet, der im Code-behind an das ViewModel weitergereicht werden kann, um beispielsweise die Navigation zur Detailseite auszulösen. Die View konzentriert sich damit auf Layout, Styling und Bindings, während das ViewModel für Datenbeschaffung und Navigationslogik zuständig ist. So bleibt die Oberfläche trotz Sidebar, Überschrift und adaptiver Liste gut strukturiert, testbar und wiederverwendbar.
Rezeptdetails und Navigation
Die Übersichtsseite liefert den Einstieg in die App, wirklich „gekocht“ wird aber in der Detailansicht. Hier entscheidet sich, ob ein Rezept gut erfassbar ist: Ein großes Bild vermittelt auf den ersten Blick, worum es geht, darunter folgen Zutaten und eine klar strukturierte Schritt-für-Schritt-Anleitung. In unserer RecipeDetailView setzen wir das mit einem einfachen, aber wirkungsvollen Layout um: Ein ScrollViewer sorgt dafür, dass auch lange Rezepte auf kleineren Bildschirmen vollständig erreichbar bleiben. Im Inneren liegt ein zentriertes Grid mit fester Maximalbreite, das den Inhalt optisch bündelt und auf Desktop wie Mobile aufgeräumt wirken lässt.
Im Zentrum steht das an das ViewModel gebundene Recipe-Objekt. Direkt unter dem Zurück-Button zeigt ein großer Bildbereich das Rezeptfoto, das über Image Source="{Binding Recipe.ImagePath}" eingebunden wird. Darunter folgt der Titel des Rezepts, gebunden an Recipe.Title, sowie ein Favoriten-Button, der visuell als Herz-Icon dargestellt ist und im Code-behind mit FavoriteButton_Click verknüpft werden kann. Auf diese Weise lassen sich Favoriten komfortabel aus der Detailansicht heraus markieren oder wieder entfernen.
Die Zutaten werden in einem separaten Abschnitt dargestellt. Eine Überschrift („Ingredients“) kündigt die Liste an, darunter gibt ein ItemsControl die Inhalte von Recipe.Ingredients aus. Jedes Element wird in einem kleinen horizontalen Layout gezeigt: links ein Kreis als Bullet (Ellipse), rechts daneben ein TextBlock mit dem eigentlichen Zutaten-Text. Das Ergebnis ist eine klare, gut lesbare Zutatenliste, die ohne zusätzliche Controls auskommt und sich vollständig über Datenbindung steuern lässt.
Die Zubereitungsschritte folgen im letzten Abschnitt unter der Überschrift „Instructions“. Hier bindet ein weiteres ItemsControl an NumberedSteps. Diese Eigenschaft kann im ViewModel beispielsweise aus der Roh-Liste der Schritte erzeugt werden, indem die einzelnen Texte mit einer automatisch generierten Schritt-Nummer kombiniert werden. So entsteht eine nummerierte Anleitung, bei der jeder Schritt als eigener TextBlock mit etwas Abstand ausgegeben wird.
Die Navigation zurück zur Übersicht wird über den BackButton gelöst, dessen Click-Event in BackButton_Click behandelt wird. Hinter diesem Event kann entweder direkt ein Frame-GoBack() aufgerufen oder ein gekapselter NavigationService verwendet werden, der die Navigation zentral verwaltet. Das State Management bleibt insgesamt überschaubar: Die Liste übergibt das gewählte Recipe als Navigationsparameter an die RecipeDetailView, dort wird es an Recipe gebunden und in allen Bereichen (Bild, Titel, Zutaten, Schritte, Favoritenstatus) wiederverwendet. Ein Favoriten-Button in der Detailansicht ist mit entsprechender Logik verknüpft und invertiert die IsFavorite-Eigenschaft des Modells. Die Detailansicht zeigt damit exemplarisch, wie Datenbindung, Navigation und plattformübergreifendes UI in Uno zusammenspielen (Listing 2).
Listing 2: Detailseite zur Anzeige der Rezepte
<Page
x:Class="SimpleRecipes.RecipeDetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SimpleRecipes"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer>
<Grid Padding="24" MaxWidth="760" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Back button -->
<Button x:Name="BackButton" Click="BackButton_Click" Content="←" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,0,8"/>
<!-- Large image / hero -->
<Border Grid.Row="1" Height="200" Background="#EEE" CornerRadius="4" Margin="0,12,0,12">
<Image Source="{Binding Recipe.ImagePath}" Stretch="UniformToFill" />
</Border>
<!-- Title + favorite -->
<Grid Grid.Row="2" Margin="0,6,0,18">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Recipe.Title}" FontSize="32" FontWeight="SemiBold" VerticalAlignment="Center" />
<Button x:Name="FavoriteButton" Grid.Column="1" Content="♡" FontSize="24" Margin="12,0,0,0" VerticalAlignment="Top" Click="FavoriteButton_Click"/>
</Grid>
<!-- Ingredients -->
<StackPanel Grid.Row="3">
<TextBlock Text="Ingredients" FontSize="20" FontWeight="SemiBold" Margin="0,6,0,8" />
<ItemsControl ItemsSource="{Binding Recipe.Ingredients}" Margin="6,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,6">
<Ellipse Width="8" Height="8" Fill="Black" VerticalAlignment="Center"/>
<TextBlock Text="{Binding}" FontSize="16" Margin="12,0,0,0" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Instructions -->
<StackPanel Grid.Row="4" Margin="0,18,0,0">
<TextBlock Text="Instructions" FontSize="20" FontWeight="SemiBold" Margin="0,6,0,8" />
<ItemsControl ItemsSource="{Binding NumberedSteps}" Margin="6,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="16" Margin="0,6" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</ScrollViewer>
</Page>
Build, Test und Cross-Platform-Ergebnisse
Nachdem die Kernfunktionalität von Simple Recipes steht und das UI vollständig implementiert ist, folgt ein entscheidender Schritt: das Testen der App auf den verschiedenen Zielplattformen (Bild 1). Die Uno Platform ermöglicht es, mit einer einzigen Codebasis sowohl Windows-Apps als auch WebAssembly-Anwendungen und mobile Apps für Android (beziehungsweise optional iOS) zu erstellen. Für den Entwicklungsprozess bedeutet das, dass die Anwendung regelmäßig auf mehreren Plattformen gestartet werden sollte, um sicherzustellen, dass das adaptive UI, das Navigationsverhalten und die Darstellung der Inhalte überall korrekt funktionieren.
Der Prototyp der App auf „großen“ und „kleinen“ Bildschirmen (Bild 1)
AutorStart auf verschiedenen Plattformen
Unter Windows läuft die App als WinUI-3-Anwendung. Dies ist oft die performanteste und zugleich komfortabelste Entwicklungsumgebung, da Fehler hier besonders schnell sichtbar werden und Hot Reload gut funktioniert. Der WebAssembly-Build wird über einen Browser gestartet. Dabei wird derselbe XAML-Code in HTML/CSS/JS übersetzt, sodass die Anwendung direkt im Web läuft, das heißt ohne zusätzliche Plug-ins. Der WASM-Build ist ideal, um die Responsiveness der App zu testen, das Verhalten in flexiblen Browserfenstern zu beobachten und sicherzustellen, dass Bilder, Schriftgrößen und Touch-Interaktionen korrekt funktionieren. Auf Android schließlich zeigt sich die mobile Nutzung der App in ihrem natürlichen Kontext. Dank des Touch-optimierten Layouts und der Einspaltenansicht wirkt die App wie eine native Android-Anwendung. Die Bedienung über Tippen, Scrollen und Wischen lässt sich besonders gut simulieren oder auf einem echten Gerät testen.
Unterschiede im Look and Feel sichtbar machen
Obwohl die Codebasis identisch ist, zeigen die Plattformen jeweils ihren eigenen Charakter:
- Windows: klare, breite Layouts, Maus- und Tastaturfokus, Sidebar wirkt wie ein Navigationsrahmen klassischer Desktop-Apps.
- WebAssembly: Flexibles Verhalten bei Resize-Ereignissen, schnelle Layoutwechsel, leicht unterschiedliche Renderdetails bei Text und Bilderhandling.
- Android: Touch-first UX, größere Bedienelemente, flüssiger Scroll-Fokus, kompaktere Darstellung.
Diese Unterschiede sind gewollt, das heißt, Uno passt die Darstellung an die jeweiligen Plattformkonventionen an, während die Struktur und Funktionalität identisch bleiben.
Zusammenfassung und Learnings
Beim Build- und Testprozess zeigen sich die wesentlichen Qualitäten der Uno Platform:
- Eine Codebasis deckt Desktop, Web und Mobile ab.
- Adaptive Layouts lassen sich mit XAML sehr einfach für verschiedene Gerätegrößen gestalten.
- Navigation und MVVM bleiben stabil und wiederverwendbar — unabhängig vom Zielsystem.
- XAML bleibt XAML, egal ob in einer WinUI-Desktop-App oder im Browser.
- Ressourcen, Bilder und Styles werden zentral verwaltet und funktionieren konsistent auf allen Plattformen.
Fazit
Die Arbeit an der Beispiel-App Simple Recipes macht deutlich, dass die Uno Platform vor allem eines bietet: Kohärenz. Statt mehrere UI-Frameworks zu beherrschen, folgt die gesamte Entwicklung einem einzigen Prinzip: WinUI/XAML. Dadurch kann sich das Team auf Gestaltung, Architektur und Benutzerfreundlichkeit konzentrieren, statt Zeit in Plattformdetails zu verlieren. Die App bildet bewusst nur den Kern eines Rezeptszenarios ab. Folgende Erweiterungen wären realistisch und sinnvoll:
- Bearbeiten und Hinzufügen von Rezepten, inklusive Eingabeformularen und Validierung.
- Anbindung an ein API-Backend, etwa um Rezepte dynamisch zu laden, Benutzerkonten zu verwalten oder Synchronisation anzubieten.
- Offline-Speicher mittels SQLite oder eingebettetem JSON-Cache, um die App unabhängig vom Netzwerk zu machen.
- Upload eigener Bilder für neue Rezepte (Mobile und Desktop). Damit kann aus der Beispielapp Schritt für Schritt eine vollwertige, produktionsreife Anwendung entstehen, ohne die Grundarchitektur ändern zu müssen.
Hinweis: Der vollständige Quellcode zu den Beispielen steht auf der Website des Autors zum Download bereit.