Teil 3: Plattformübergreifende App-Entwicklung mit dem MVVMbasics Framework

Gastbeitrag von Andreas Kuntner

Dieser Artikel ist der zweite 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/

Definition der Views

Ich habe beide Pages im Shared-Projekt angelegt, es wäre aber genauso möglich für beide Plattformen unterschiedliche Pages zu definieren und diese im jeweiligen Windows- bzw. WindowsPhone-Projekt zu hinterlegen. Wichtig ist, dass alle Views (egal ob WinRT-Page oder WPF-Fenster) von der BaseView-Klasse abgeleitet sind, und zwar sowohl im XAML- als auch im C#-Code. In XAML bietet es sich außerdem an, das entsprechende Viewmodel als Design-Time-DataContext anzugeben, denn dadurch können Properties und Commands mit Hilfe von IntelliSense schneller gefunden werden:

 <mvvmBasics:BaseView
    x:Class="UniversalApp.Views.LoginPage"
   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="using:UniversalApp"
 xmlns:mvvmBasics="using:MVVMbasics.Views"
    xmlns:viewmodels="using:SimpleChat.Viewmodels"
   xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
   mc:Ignorable="d"
 d:DataContext="{d:DesignInstance viewmodels:LoginViewmodel}">

Auf keinen Fall sollte die normale DataContext Eigenschaft der View manuell mit einer Instanz des Viewmodels befüllt werden, darum kümmert sich das NavigatorService automatisch.

Im C#-Code der View ist es außerdem notwendig, das [MvvmNavigationTarget] Attribut zu setzen. Dieses sagt dem NavigationService während der automatischen Registrierung, dass es sich bei dieser Klasse um eine View handelt und mit welchem Viewmodel sie verbunden werden soll. Abgesehen davon sollte aber – ganz dem MVVM-Pattern folgend – nichts im C#-Coder der Views stehen:

 [MvvmNavigationTarget(typeof(LoginViewmodel))]
public sealed partial class LoginPage : BaseView
{
    public LoginPage()
 {
       this.InitializeComponent();
    }
}

In XAML können nun die beiden Pages nach Lust und Laune gestaltet werden. Um auf Properties oder Commands des jeweiligen Viewmodels zuzugreifen, kann wie gewohnt Binding verwendet werden. Die beiden Textfelder zur Eingabe von Namen und Alter sowie der Login-Button schauen in meiner Demo beispielsweise so aus:

 <TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" InputScope="Number" />
<Button Command="{Binding LoginCommand}">Einloggen</Button>

Die Angabe von UpdateSourceTrigger=PropertyChanged im Fall der beiden Textboxen ist notwendig, um Änderungen innerhalb einer Textbox noch während dem Tippen ans Viewmodel weitergeleitet werden, und nicht erst wenn die Textbox den Focus verliert. Dadurch wird auch die Bedingung des Button-Commands sofort während dem Tippen überprüft und der Button sozusagen in Echtzeit aktiviert bzw. deaktiviert!

Das genaue Design beider Pages ist im vollständigen Projekt zu finden. Wenn wir die App jetzt laufen lassen (und ein passendes Webservice erreichbar ist), können wir Nachrichten zwischen Windows 8 und Windows Phone Geräten austauschen.

Eine zusätzliche Zielplattform?

Die Windows 8 und Windows Phone Apps schauen ja schon mal ganz gut aus, aber einige meiner Kollegen würden gern den Chatverlauf immer im Blick haben während sie mit Desktop-Programmen arbeiten. Eine SimpleChat-Version für den Desktop? Wenn das ursprüngliche Projekt wie in diesem Artikel beschrieben korrekt für die plattformübergreifende Entwicklung strukturiert wurde, ist das in wenigen Minuten erledigt!

Um das auszuprobieren, fügen wir schnell mal ein Projekt vom Typ „WPF Application“ zur Solution hinzu. Dieses muss natürlich eine Referenz auf das PCL-Projekt sowie auf das MVVMbasics NuGet-Package bekommen. Diesmal sollte in den References die MVVMbasicsWPF Biblitothek zusätzlich zum plattformübergreifenden MVVMbasics aufscheinen.

Wir passen die existierende App-Klasse so an, dass sie von BaseApplication erbt, und registrieren alle benötigten Services – am besten im Constructor der App:

 public partial class App : BaseApplication
{
    public App()
   {
       var navigator = new NavigatorService();
        navigator.RegisterAll("DesktopApp.Views");
     ServiceLocator.Register(navigator);
     ServiceLocator.Register<TimerService>();
      ServiceLocator.Register<MessageboxService>();
     ServiceLocator.Register<BackendService>();
    }
}

Dann erstellen wir zwei neue Fenster im Views Namespace, ändern ihre Definition sodass sie von BaseView statt von Window ableiten, und fügen das [MvvmNavigationTarget] Attribut hinzu:

 [MvvmNavigationTarget(typeof(LoginViewmodel))]
public partial class LoginWindow : BaseView
{
   public LoginWindow()
   {
       InitializeComponent();
  }
}

Der Inhalt der beiden Fenster kann jetzt komplett neu designed oder aus dem existierenden Universal App Projekt kopiert und überarbeitet werden – das wichtigste ist: Datenmodelle, Viewmodels und die gesamte Businesslogik können so wie sie sind weiterverwendet werden, so ist die WPF-App im Handumdrehen fertig!

 

Event Binding

Eine Kleinigkeit möchte ich zum Abschluss noch zeigen: Was, wenn wir in der WPF-App keinen eigenen Button zum Aktualisieren der Nachrichten-Liste integrieren wollen, sondern die Liste bei jedem Berühren mit der Maus neu befüllen wollen?

Klar, um das Herunterladen und Aktualisieren der Nachrichten kümmert sich das Refresh Command, das im ChatViewmodel ja schon vorhanden ist. Um dieses Command aber zu starten, muss erst das MouseEnter-Event der Liste in XAML spezifiziert werden, dann wird ein Eventhandler im C#-Code des Fensters erzeugt, und mit etwas Geduld können wir von dort auf das Viewmodel zugreifen und das Command ausführen – das ganze ist aber mehr als mühsam, entspricht nicht dem MVVM-Pattern, und muss vor allem für jede Plattform individuell nachgezogen werden.

MVVMbasics 2.0 bietet hier erstmals eine bequemere Lösung: Im XAML-Code wird als Eventhandler des MouseEnter-Events einfach die EventToCommand Methode angegeben, die im MVVMbasics Framework definiert ist und für alle Plattformen zur Verfügung steht. Jetzt kann das Command ganz einfach an Event.Binding gebunden werden:

 MouseEnter="EventToCommand" mvvm:Event.Command="{Binding RefreshCommand}"

Hinweis: Event.Binding ist im Namespace MVVMbasics.Helpers definiert, dieser muss im Fenster deklariert sein:

 xmlns:mvvm="clr-namespace:MVVMbasics.Helpers;assembly=MVVMbasicsWPF"

Weiterführende Informationen

Übrigens: Zusätzlich zu WinRT und WPF unterstützt MVVMbasics auch die Windows Phone Silverlight Plattform, sodass die SimpleChat App ganz schnell auch für Windows Phone 8.0 angepasst werden könnte – und ich arbeite laufend daran, dass es hoffentlich bald noch mehr Plattformen werden!

Ich hoffe, dieser Artikel hat euch einen kleinen Einblick in die Arbeit mit MVVMbasics gegeben. Das Framework ist kostenlos als NuGet Paket verfügbar und kann in jedes Projekt eingebunden werden: https://www.nuget.org/packages/MVVMbasics

Viele der nötigen Set-Up Schritte die in diesem Artikel beschrieben sind und sich bei jedem Projekt wiederholen könnt ihr umgehen, indem ihr die MVVMbasics Project Templates verwendet. Diese können unter https://visualstudiogallery.msdn.microsoft.com/d67eb2e6-c497-4ad1-bd62-77aa08c7e18f heruntergeladen werden.

Weiterführende Infos zu den Templates und zum Framework, aber auch Tutorials, Dokumentation etc. gibt’s unter der Adresse https://mvvmbasics.mobilemotion.eu/

Und falls jemand wissen will wie das eine oder andere Feature gelöst ist (oder MVVMbasics für weitere Plattformen anpassen will): Der gesamte Sourcecode ist frei verfügbar unter https://mvvmbasics.codeplex.com/SourceControl/latest