Syncfusion hat sich in der Vergangenheit als Standardanbieter für allerlei Arten von Steuerelementbibliotheken für die .NET-Entwicklung etabliert. Mit Syncfusion Toolkit for .NET MAUI steht nun ein neues Produkt zur Verfügung, das sich an die Bedürfnisse von MAUI-Entwickler:innen richtet.
Der Start von Microsofts MAUI-Plattform verlief nicht unbedingt glatt. Die praktische Erfahrung des Autors, der das System zu seinem Leidwesen seit einigen Jahren produktiv einsetzt, beweist allerdings eine erhebliche Besserung der Lage in den letzten Monaten (Informationen über den Kampf zwischen iOS und .NET finden sich unter [1]).
Mit Syncfusion Toolkit for .NET MAUI steht eine kostenlose Steuerelementbibliothek zur Verfügung, die auf die Bedürfnisse von Datenvisualisierung und Animation ausgerichtet ist. In diesem Artikel wollen wir einen Teil der Funktionalität vorstellen und ein Gefühl für die bei der Nutzung anzutreffenden Design Patterns schaffen.
Aus Gründen der Bequemlichkeit werden wir in den folgenden Schritten vor allem Windows als Zielsystem verwenden – ein Android-Smartphone kommt manchmal ebenfalls zum Einsatz.
Starten Sie Visual Studio 2022 und erzeugen Sie ein neues Projekt auf Basis der Vorlage für MAUI-Applikationen. Als Name vergibt der Autor in den folgenden Schritten „SUSSyncfusionDemo1“; im Bereich der unterstützten Version des .NET Framework wollen wir uns für die Version 8.0 entscheiden.
Im Bereich der Hardwarekompatibilität zeigt sich das Toolkit dabei durchaus tolerant (Abb. 1). Im Interesse der Bequemlichkeit wird der Autor in den folgenden Schritten auf sein Samsung A33 5G setzen. Die praktische Erfahrung lehrt, dass preiswerte Geräte wegen der geringen Rechenleistung hilfreich beim Aufspüren von Performanceproblemen sind; dank der 5-Jahres-Updategarantie gilt außerdem, dass die Hardware lang verwendbar bleibt.
Abb. 1: Das SyncFusion-Toolkit ist im Bereich Hardwareanforderungen anspruchslos
Nächste Amtshandlung ist das Öffnen der NuGet-Paketverwaltung, wo wir nach dem String Syncfusion.Maui.Toolkit suchen. Zum Zeitpunkt der Veröffentlichung dieses Artikels ist die Version 1.0.1 der Steuerelementebibliothek aktuell.
Nach dem Deployment des Pakets ist es auf jeden Fall erforderlich, in die Datei MauiProgram.cs zu wechseln und vor der Initialisierung der Schriftarten die hier gezeigte Initialisierungsmethode für das Framework aufzurufen:
public static MauiApp CreateMauiApp() {
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureSyncfusionToolkit()
Im Rahmen der Inbetriebnahme der diversen Widgets ist es meist erforderlich, im Markup bestimmte Namespaces zu deklarieren. In diesem Artikel werden wir die benötigten Deklarationen angeben – möchten Sie ein anderes im Framework enthaltenes Widget benutzen, findet sich Weiteres unter [2].
Von der Seite einblendbare Menüs mit zusätzlichen Optionen haben sich als Design Pattern zur Vereinfachung der Benutzerschnittstellen von Mobilcomputer- und sonstigen Applikationen etabliert. In unserem Framework steht eine diesbezügliche Komponente zur Verfügung, die wir in Betrieb nehmen wollen. Hierzu ist das Öffnen der XAML-Datei erforderlich, die im ersten Schritt den Namespace xmlns:navigationdrawer="clr-namespace:Syncfusion.Maui.Toolkit.NavigationDrawer;assembly=Syncfusion.Maui.Toolkit" aufnimmt. Zu beachten ist, dass die kommerzielle Variante der Steuerelementbibliothek im Gutteil der APIs kommunal ist – wichtig ist, immer die Toolkit-Version zu verwenden, sofern die Nutzung der quelloffenen Variante avisiert ist.
Der Rest der View präsentiert sich raumgreifend und kommt direkt in der ContentPage zum Liegen. Erste Amtshandlung ist dabei einerseits das Erzeugen des eigentlichen Steuerelements mit dem Namen SfNavigationDrawer, das im Rahmen der Initialisierung für die Inbetriebnahme erforderliche Attribute aufnimmt (Listing 1).
Listing 1
<navigationdrawer:SfNavigationDrawer x:Name="navigationDrawer">
<navigationdrawer:SfNavigationDrawer.DrawerSettings>
<navigationdrawer:DrawerSettings DrawerWidth="300" ="160">
<navigationdrawer:DrawerSettings.DrawerHeaderView>
<Grid BackgroundColor="#1aa1d6" RowDefinitions="120,40">
<Label Text="Das musikalische Fenster"
Grid.Row="1"
. . .
TextColor="White"/>
</Grid>
</navigationdrawer:DrawerSettings.DrawerHeaderView>
Von besonderer Bedeutung ist hier das DrawSettings-Objekt, das Parameter für die bei Bedarf eingeblendete Menü-Pane festgelegt. Neben dem Bestimmen der Dimensionen über die Attribute DrawerWidth und DrawerHeaderHeight schreiben wir hier in der DrawHeaderView ein Grid mit einem Steuerelement ein. Das Framework wird dieses später zur Realisierung der Kopfzeile des Navigationsfensters heranziehen.
Listing 2
<navigationdrawer:DrawerSettings.DrawerContentView>
<ListView x:Name="listView"
ItemSelected="listView_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<VerticalStackLayout HeightRequest="60">
<Label Margin="10,7,0,0"
Text="{Binding}"
FontSize="16"/>
</VerticalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</navigationdrawer:DrawerSettings.DrawerContentView>
</navigationdrawer:DrawerSettings>
</navigationdrawer:SfNavigationDrawer.DrawerSettings>
Der nächste Akt ist das Festlegen der Elemente, die im eingeblendeten Fenster anzuzeigen sind (Listing 2). Die DrawContentView ist analog zu ihrer soeben besprochenen Kollegin DrawHeaderView ein Container, der beliebige Widgets aufnehmen kann. Zu beachten ist, dass die hier gezeigte Beladung mit nur einem Steuerelement nicht verpflichtend ist – in der Dokumentation zeigt Syncfusion verschiedenste Beispiele, in denen zwei oder mehr Widgets in den jeweiligen Slots unterkommen.
Im Fall der hier demonstrierten Variante entsteht eine ListView, die über die einzelnen Items die im Menü anzubietenden Optionen aufnimmt. Damit sind wir auch schon zur Realisierung des Fleisches der View bereit (Listing 3).
Listing 3
<navigationdrawer:SfNavigationDrawer.ContentView>
<Grid x:Name="mainContentView"
BackgroundColor="White" RowDefinitions="Auto,*">
<HorizontalStackLayout BackgroundColor="#1aa1d6" Spacing="10" Padding="5,0,0,0">
<Button x:Name="hamburgerButton"
HeightRequest="50"
WidthRequest="50"
HorizontalOptions="Start"
Text="Go"
BackgroundColor="#1aa1d6"
Clicked="hamburgerButton_Clicked"/>
<Label x:Name="headerLabel"
HeightRequest="50"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Text="Home" FontSize="16"
TextColor="White"
BackgroundColor="#1aa1d6"/>
</HorizontalStackLayout>
<Label Grid.Row="1"
. . .
TextColor="Black"/>
</Grid>
</navigationdrawer:SfNavigationDrawer.ContentView>
</navigationdrawer:SfNavigationDrawer>
Die Nutzung eines Grids samt einem Layout ermöglicht die dynamische Anordnung der Steuerelemente. Der Button und das Label realisieren eine Kopfzeile, während das untere Label das anzuzeigende Inhaltselement festlegt.
Zur Inbetriebnahme des Systems ist im Konstruktor der MainPage das Einschreiben von Dummy Items erforderlich:
List<string> list = ["Option A", "Option B", "Option C"];
listView.ItemsSource = list;
Sowohl der für die Aktivierung vorgesehene Knopf als auch die einzelnen ListView Items müssen einen Event Handler aufnehmen, der wiederum die Methode toggleDrawer aufnimmt (Listing 4).
Listing 4
private void listView_ItemSelected(object sender, SelectedItemChangedEventArgs e) {
navigationDrawer.ToggleDrawer();
}
private void hamburgerButton_Clicked(object sender, EventArgs e) {
navigationDrawer.ToggleDrawer();
}
Danach ist das Programm zur Ausführung bereit. Abbildung 2 und 3 zeigen, dass alles wie erwartet funktioniert.
Abb. 2: Das Menü im deaktivierten ...
Abb. 3: ... und im aktivierten Zustand
Zu beachten ist, dass Einblendung und Ausblendung der Navigationsleiste mit Animationen erfolgen – diese sind in einem Magazin naturgemäß nicht darstellbar.
Dass Cross-Platform-Systeme besonders für die Erzeugung von Geschäftsapplikationen geeignet sind, bedarf nur nebenläufiger Erwähnung. Einer der stärksten Aspekte des Toolkits ist, dass es der Entwicklerschaft eine Gruppe von zur Verfügung stehenden Charting-Widgets kostenlos anbietet. Als nächste Aufgabe wollen wir unser Beispiel um ein Tortendiagramm ergänzen. Hierzu ist im ersten Schritt das Hinzufügen der Deklaration xmlns:chart="clr-namespace:Syncfusion.Maui.Toolkit.Charts;assembly=Syncfusion.Maui.Toolkit" im Header der XAML-Datei erforderlich.
Im nächsten Schritt muss das unter Grid.Row="1" eingebuchte Label-Steuerelement ersetzt werden. Wir hatten im vorherigen Schritt ja festgestellt, dass das Grid für den gesamten Bildschirminhalt verantwortlich ist. Anstatt des bisher platzierten Steuerelements positionieren wir nun ein anderes Objekt wie in Listing 5.
Listing 5
<chart:SfCircularChart Grid.Row="1" x:Name="pieChart">
. . .
<chart:PieSeries ItemsSource="{Binding Saufmittel}"
ShowDataLabels="True"
XBindingPath="Product"
EnableTooltip="True"
ExplodeAll="True"
YBindingPath="Sales"/>
</chart:SfCircularChart>
Die Klasse SfCircularChart dient als Container, der Diagrammobjekte aufnimmt. Der eigentlich dargestellte Diagrammkomplex wird dann über ein PieSeries-Objekt bereitgestellt, in dem wir als Erstes neben der Anzeige der Data-Labels auch die Data-Binding-Beziehungen zur Beschaffung der anzuzeigenden Formationen festlegen.
Die Exponierung der notwendigen Informationen erfolgt über Data Binding in der MainPage, was den nach folgendem Schema aufzubauenden Code voraussetzt:
Saufmittel = new List<GetraenkeModell>() {
new GetraenkeModell(){Product = "Bier", Sales = 70},
. . .
};
this.BindingContext = this;
Im Hintergrund findet sich eine Storage-Klasse:
public class GetraenkeModell {
public string? Product { get; set; }
public double Sales { get; set; }
}
An dieser Stelle ist diese Version des Programms zur Ausführung bereit. Abbildung 4 zeigt, dass das Tortendiagramm auf dem Bildschirm erscheint.
Abb. 4: Die Visualisierungs-Engine hat ihre Schuldigkeit getan
Ein interessanter Diversifikationsfaktor von Diagrammbibliotheken ist die Art und Weise, wie Elemente in das Diagramm einzupflegen sind. Im Fall von Syncfusion entstehen Legenden und ähnliche Hilfsobjekte durch das Einspielen zusätzlicher XAML-Elemente. Wer beispielsweise einen Header und eine Legende hinzufügen möchte, muss die Deklaration nach dem in Listing 6 gezeigten Schema ergänzen.
Listing 6
<chart:SfCircularChart Grid.Row="1" x:Name="pieChart">
<chart:SfCircularChart.Title>
<Label Text="SAU-FEN!"/>
</chart:SfCircularChart.Title>
<chart:SfCircularChart.Legend>
<chart:ChartLegend/>
</chart:SfCircularChart.Legend>
Zu beachten ist, dass der in SfCircularChart.Title angelegte Header einfach nur einen Streifen auf der Oberseite des Formulars darstellt. Möchten Entwickler:innen die Inhalte zentrieren, so ist ein zusätzliches Layout erforderlich. Die Ausführung im vorliegenden Zustand führt dann jedenfalls zum in Abbildung 5 gezeigten Verhalten.
Abb. 5: Das Diagramm wurde um Kontext erweitert
Interessant ist in diesem Zusammenhang die Frage, wie die View auf das erfolgende Einpflegen von mehreren Diagrammen reagiert:
<chart:DoughnutSeries ItemsSource="{Binding Saufmittel}"
. . .
<chart:PieSeries ItemsSource="{Binding Saufmittel}"
. . .
</chart:SfCircularChart>
Wer hier keine zusätzlichen Layoutinformationen anliefert, bekommt ein zusammengefügtes Diagramm – Syncfusion rendert die einzelnen Elemente einfach übereinander, was nicht unbedingt im Interesse der Entwickler:innen ist.
In Applikationen, die dynamisch aktualisierbare Informationen anzeigen, hat sich eine Wischgeste von oben nach unten als Auslöserhandlung für ressourcenintensive Aktualisierungen etabliert. In Syncfusion findet sich ein Steuerelement, dass diese Operation umsetzbar macht.
Zur Nutzung dieses Features ist es im ersten Schritt erforderlich, die XAML-Namespacedeklaration xmlns:PullToRefreshControl="clr-namespace:Syncfusion.Maui.Toolkit.PullToRefresh;assembly=Syncfusion.Maui.Toolkit" hinzuzufügen. Die eigentliche Inbetriebnahme des Steuerelements erfolgt durch Einschreiben eines weiteren Containers. Da wir ja bereits eine vergleichsweise komplizierte Steuerelementstruktur verwalten, schreiben wir die beiden neuen Elemente einfach in das schon vorhandenen NavigationDrawer-Element (Listing 7).
Listing 7
<PullToRefreshControl:SfPullToRefresh.PullableContent>
<navigationdrawer:SfNavigationDrawer x:Name="navigationDrawer">
. . .
</navigationdrawer:SfNavigationDrawer>
</PullToRefreshControl:SfPullToRefresh.PullableContent>
</PullToRefreshControl:SfPullToRefresh>
Damit ist die Integration abgeschlossen. Eine Wischgeste von oben nach unten führt nun zur Einblendung des runden Updating-Symbols.
Von Haus aus wird der Rest des Formulars in diesem Betriebsmodus nicht bewegt. Ist eine komplette Verschiebung der Formularinhalte gewünscht, muss zusätzlich die Eigenschaft TransitionMode gesetzt werden:
<PullToRefreshControl:SfPullToRefresh x:Name="pullToRefresh" TransitionMode="Push">
Unschön ist, dass das Programm im derzeitigen Zustand nach dem Loslassen des Mauszeigers sofort wieder in den normalen Betriebszustand zurückkehrt. Möchte man die Aktualisierungsanimation einige Zeit auf dem Bildschirm halten, so ist ein manueller Eingriff in die Event-Verarbeitung erforderlich.
Im ersten Schritt ist es notwendig, einen Event Handler für das Refreshing Event anzulegen. Der Autor wird das in den folgenden Schritten aus Gründen der Bequemlichkeit im Code-behind durchführen – angemerkt sei, dass es in XAML natürlich ebenfalls diesbezügliche Delegaten gibt:
pullToRefresh.Refreshing += OnPullToRefreshRefreshing;
Das Event wird vom Syncfusion-GUI-Stack immer dann ausgelöst, wenn die Aktualisierungsgeste vom Benutzenden vollendet wurde. Eine Beispielimplementierung, die die Darstellung der Animation sicherstellt, würde demnach folgendermaßen aussehen:
private async void OnPullToRefreshRefreshing(object sender, EventArgs args) {
pullToRefresh.IsRefreshing = true;
await Task.Delay(2000);
pullToRefresh.IsRefreshing = false;
}
Zu beachten ist, dass die Animation nur so lange läuft, wie der Wert von IsRefreshing auf true gesetzt ist. Abbildung 6 zeigt dann, wie sich die Ergebnisse auf der Workstation des Autors präsentieren.
Abb. 6: Die Animation ist nun klar sichtbar
Spätestens seit den von Apple eingeführten Radio-Button-Steuerelementen gilt, dass Benutzer:innen auch im Bereich der Dateneingabe verschiedene Komfortfunktionen erwarten. Im Syncfusion-.NET-MAUI-Toolkit finden sich einige der Steuerelemente, die die Realisierung erleichtern.
Als ersten Versuch wollen wir eine Textbox einbinden, die zur Reduktion von Kundenanfragen und auch zur Einblendung von Hilfsinformationen befähigt ist. Im ersten Schritt muss wie immer im XAML-Header die Namespace-Deklaration xmlns:inputLayout="clr-namespace:Syncfusion.Maui.Toolkit.TextInputLayout;assembly=Syncfusion.Maui.Toolkit" eingefügt werden.
Im nächsten Schritt müssen wir das weiter oben errichtete Navigation-Scaffolding zum tatsächlichen Navigieren befähigen. Die Syncfusion-Implementierung hält sich aus der eigentlichen Sichtbarkeitsverwaltung heraus, weshalb wir im ersten Schritt ein zusätzliches Objekt vom Typ VerticalStackLayout im eigentlich für die Kinderwidgets vorgesehenen Feld anlegen.
Zu beachten ist, dass die Eigenschaft Grid.Row="1" identisch mit dem Container des Charts ist – wir wollen ja, dass die Layout-Engine die beiden Steuerelemente gleichrangig bildschirmfüllend rendert (Listing 8).
Listing 8
<VerticalStackLayout Grid.Row="1" IsVisible="False" x:Name="workBox">
<inputLayout:SfTextInputLayout Hint="Belieben zu Fauchen?">
<Entry />
</inputLayout:SfTextInputLayout>
</VerticalStackLayout>
<chart:SfCircularChart Grid.Row="1" x:Name="pieChart">
Die weiter oben angelegte Ereignisverarbeitungsroutine bekommt dann zusätzlichen Code eingeblendet, der das aktive Element ausblendet und die Sichtbarkeit des anderen Elements auf True setzt (Listing 9).
Listing 9
private void listView_ItemSelected(object sender, SelectedItemChangedEventArgs e) {
if (e.SelectedItemIndex == 1) {
pieChart.IsVisible = false;
workBox.IsVisible = true;
}
navigationDrawer.ToggleDrawer();
}
An dieser Stelle ist diese Version des Programms zur Ausführung bereit. Abbildung 7 und 8 zeigen, wie sich das Aussehen der beiden Textboxen nach der Eingabe von Informationen verändert.
Abb. 7: Das Steuerelement vor ...
Abb. 8: ... und nach der Dateneingabe
Als nächstes Steuerelement wollen wir uns das SegmentedControl ansehen. Dabei handelt es sich um ein Widget, das – analog zu einem grafisch übersichtlichen Radiobutton – mehrere Auswahlfelder in einem Element zusammenfasst (Abb. 9).
Abb. 9: Das SegmentedControl versucht, die Übersichtlichkeit zu erhöhen
Zur Realisierung dieses Widgets müssen wir im ersten Schritt abermals in die XAML-Datei wechseln, in deren Header nun die zusätzliche Deklaration xmlns:segmentedControl="clr-namespace:Syncfusion.Maui.Toolkit.SegmentedControl;assembly=Syncfusion.Maui.Toolkit" unterzubringen ist.
Das eigentliche SegmentedControl entsteht in seiner einfachsten Form durch Deklarieren einer ItemsSource, die die in Abbildung 9 gezeigten Arten der Musikerzeugung zusammenfasst (Listing 10).
Listing 10
<segmentedControl:SfSegmentedControl>
<segmentedControl:SfSegmentedControl.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Lästig</x:String>
<x:String>Nervend</x:String>
<x:String>Unerträglich</x:String>
</x:Array>
</segmentedControl:SfSegmentedControl.ItemsSource>
</segmentedControl:SfSegmentedControl>
Zu beachten ist, dass das SegmentedControl nicht auf die Darstellung reiner Textelemente beschränkt ist. Wer eine nach folgendem Schema angelegte Klasse im Code-behind deklariert, kann auch kleine Grafiken einblenden (weitere Informationen zu den Möglichkeiten in diesem Fall finden sich in der Dokumentation):
public List<SfSegmentItem> SegmentItems { get; set; }
public ViewModel() {
SegmentItems = new List<SfSegmentItem>() {
new SfSegmentItem() { ImageSource="jackson.png", Text="Jack" },
Obwohl wir schon aus Platzgründen auf einige interessante Features des Syncfusion Toolkits nicht eingehen konnten, sollte dieser Artikel die Schlagkräftigkeit des GUI-Stacks demonstrieren und die bei der Nutzung anzutreffenden Vorgehensweisen demonstrieren. Der Autor hofft, dass die gezeigten Funktionen eigenes Interesse an tiefergehenden Experimenten wecken.
[1] https://www.youtube.com/user/MrTamhan
[2] https://help.syncfusion.com/maui-toolkit/introduction/overview