Teil 3: Windows 10 Apps mit MVVMbasics

Gastbeitrag von Andreas Kuntner

Dieser Artikel ist der dritte Teil einer dreiteiligen Serie.

Andreas Kuntner arbeitet als .NET Entwickler mit Schwerpunkt auf Client-, UI- und Mobile Development, und betreut in seiner Freizeit das MVVMbasics Framework und den Developer-Blog https://blog.mobilemotion.eu/

Data Binding andersrum:

Natürlich kann Data Binding auch in die andere Richtung verwendet werden, nämlich um durch den Benutzer eingegebene Daten im Viewmodel zu speichern (und weiterzuverarbeiten). Setzen wir testweise etwa folgendes Szenario um: Auf der MainPage wird der Benutzer gebeten, seinen Namen einzugeben. Dieser Name wird dann ins Viewmodel übertragen und dort weiterverwendet (wie genau, das sehen wir im nächsten Abschnitt...).

Dazu brauchen wir lediglich im MainViewmodel ein public Property vom Typ string, das als MvvmBindable markiert ist:

1 2 [MvvmBindable] public string Name { get; set; }

... und ein entsprechendes Eingabefeld in der View. Nachdem das Binding diesmal in beide Richtung funktionieren soll, fügen wir Mode=TwoWay an:

1 2 3 4 5 <StackPanel Orientation="Vertical">     <TextBlock>Enter your name:</TextBlock>     <TextBox Text="{x:Bind Vm.Name, Mode=TwoWay}"/>     <Button Command="{x:Bind Vm.SampleCommand}">Show detail page</Button> </StackPanel>

Der Name des Benutzers müsste jetzt also im MainViewmodel vorhanden sein, aber wie überprüfen wir das? Geben wir ihn doch während der Navigation an das DetailViewmodel weiter und lassen ihn dort anzeigen!

Der erste Teil dieser Aufgabe ist sehr einfach: Der NavigateTo-Methode des NavigatorService kann ein beliebiger Parameter mitgegeben werden, dieser muss nur mit einem eindeutigen Bezeichner versehen werden - da es sich hier um den Namen des Benutzers handelt, nennen wir ihn doch einfach "UserName":

1 _navigatorService.NavigateTo<DetailViewmodel>("UserName", this.Name);

Am Ziel, nämlich im DetailViewmodel, kommt dieser Parameter innerhalb der OnNavigatedTo-Methode an - diese wird automatisch immer dann aufgerufen, wenn die zugehörige Page am Bildschirm erscheint. Nachdem wir den Parameter nur dann auslesen wollen, wenn die Page tatsächlich neu geladen wird (und nicht, wenn sie etwa nur deshalb wieder auf den Screen kommt weil die App minimiert oder durch ein anderes Fenster verdeckt war), prüfen wir hier zusätzlich den ViewState:

1 2 3 4 5 6 7 8 9 10 11 12 public override void OnNavigatedTo(ParameterList uriParameters, ParameterList parameters, ViewState viewState) {     base.OnNavigatedTo(uriParameters, parameters, viewState);     if (viewState == ViewState.Activated)     {         string userName;         if (parameters.TryGet<string>("UserName", out userName))         {             MyText += userName;         }     } }

Die uriParameters-Variable ist nur noch zur Abwärtskompatibilität mit Windows Phone Silverlight Projekten vorhanden und wird unter Windows 10 leer sein, uns interessiert viel mehr das Feld parameters: Dieses müsste einen Wert mit Namen "UserName" beinhalten - in diesem Fall hängen wir diesen Wert einfach an MyText an, sodass der Benutzername sofort am Screen zu sehen sein müsste!

Toll wäre es jetzt noch, wenn man ohne einen Namen eingegeben zu haben gar nicht zur DetailPage weiternavigieren könnte. Einer der großen Vorteile von MVVMbasics ist, dass ein solches Verhalten mit weniger als einer Zeile Code zu bewerkstelligen ist: Wir fügen bei der Instanzierung des Commands (innerhalb der CreateCommand-Methode) einfach eine Bedingung hinzu:

1 SampleCommand = CreateCommand(SampleMethod, () => !String.IsNullOrEmpty(Name));

Um den Rest kümmert sich das Framework automatisch - es erkennt, dass das SampleCommand jetzt vom Name Property abhängt, und aktualisiert jedes Mal wenn sich der Inhalt des Name-Propertys ändert automatisch das Command. In der App wird das durch ausgrauen des Buttons visualisiert, wodurch sehr elegant eine Navigation ohne Eingabe verhindert wird.

 

Asynchrone Commands:

Seit .NET 4.5 sollten zeitaufwändige (Hintergrund-) Operationen mit den async bzw. await Schlüsselwörtern gekennzeichnet und asynchron ausgeführt werden. Dies gilt natürlich auch für Operationen, die durch ein Command ausgelöst werden. Bisher war es allerdings nicht möglich, Command-Methoden einen anderen Rückgabetyp als void zu verpassen, was zu unschönen Methodensignaturen führt wie etwa:

1 private async void DoSomething()

Eigentlich sollte eine solche asynchrone Methode einen Rückgabewert vom Typ Task haben. Mit der neuen Version von MVVMbasics ist das jetzt möglich - der einzige Unterschied ist, dass das jeweilige Command mit der Methode CreateAsyncCommand instanziert wird.

Stellen wir uns vor, unser SampleCommand führt irgendeine länger dauernde Operation aus, bevor zur DetailPage navigiert wird - zum Testen tut es auch ein Aufruf von Task.Delay:

1 2 3 4 5 private async Task SampleMethod() {     await Task.Delay(TimeSpan.FromSeconds(2));     _navigatorService.NavigateTo<DetailViewmodel>("UserName", Name); }

Die Instanzierung im Constructor des Viewmodels ändern wir einfach in:

1 SampleCommand = CreateAsyncCommand(SampleMethod, () => !String.IsNullOrEmpty(Name));

Die Methode returniert jetzt einen Task (statt void), was für die Ausführung der App zwar egal ist, aber das Testen von Viewmodel-Methoden z.B. mit Hilfe von Unit Tests vereinfacht.

 

Ausblick:

Auch wenn das nur ein kleines und stark vereinfachtes Beispiel war, ich hoffe die Projektstruktur und die Grundzüge der Architektur sind klar geworden. Natürlich bleibt auch die Cross-Plattform-Fähigkeit voll erhalten, das heißt es wäre mit relativ wenig Aufwand möglich, die bestehende Funktionalität in den Viewmodels auf weiteren Plattformen wiederzuverwenden, und die App etwa auf Xamarin oder WPF zu portieren - aber das ist eine andere Geschichte!