Teil 5. Von Datenbindungen zu MVVM

Beispiel herunterladen Das Beispiel herunterladen

Das Architekturmuster Model-View-ViewModel (MVVM) wurde unter Berücksichtigung von XAML erfunden. Das Muster erzwingt eine Trennung zwischen drei Softwareebenen – der XAML-Benutzeroberfläche, die als Ansicht bezeichnet wird; die zugrunde liegenden Daten, als Modell bezeichnet; und ein Vermittler zwischen der Ansicht und dem Modell, das als ViewModel bezeichnet wird. View und ViewModel sind häufig über in der XAML-Datei definierte Datenbindungen verbunden. Der BindingContext für die Ansicht ist in der Regel ein instance des ViewModel.

Ein einfaches ViewModel

Als Einführung in ViewModels sehen wir uns zunächst ein Programm ohne an. Zuvor haben Sie erfahren, wie Sie eine neue XML-Namespacedeklaration definieren, damit eine XAML-Datei auf Klassen in anderen Assemblys verweisen kann. Im Folgenden sehen Sie ein Programm, das eine XML-Namespacedeklaration für den System Namespace definiert:

xmlns:sys="clr-namespace:System;assembly=netstandard"

Das Programm kann verwendenx:Static, um das aktuelle Datum und die aktuelle Uhrzeit von der statischen DateTime.Now -Eigenschaft abzurufen und diesen DateTime Wert auf festzulegen, auf StackLayout:BindingContext

<StackLayout BindingContext="{x:Static sys:DateTime.Now}" …>

BindingContext ist eine besondere Eigenschaft: Wenn Sie die für BindingContext ein Element festlegen, wird es von allen untergeordneten Elementen dieses Elements geerbt. Dies bedeutet, dass alle untergeordneten Elemente von StackLayout den gleichen BindingContextaufweisen und einfache Bindungen an Eigenschaften dieses Objekts enthalten können.

Im One-Shot DateTime-Programm enthalten zwei der untergeordneten Elemente Bindungen an Eigenschaften dieses DateTime Werts, aber zwei andere untergeordnete Elemente enthalten Bindungen, denen anscheinend ein Bindungspfad fehlt. Dies bedeutet, dass der DateTime Wert selbst für verwendet StringFormatwird:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:sys="clr-namespace:System;assembly=netstandard"
             x:Class="XamlSamples.OneShotDateTimePage"
             Title="One-Shot DateTime Page">

    <StackLayout BindingContext="{x:Static sys:DateTime.Now}"
                 HorizontalOptions="Center"
                 VerticalOptions="Center">

        <Label Text="{Binding Year, StringFormat='The year is {0}'}" />
        <Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
        <Label Text="{Binding Day, StringFormat='The day is {0}'}" />
        <Label Text="{Binding StringFormat='The time is {0:T}'}" />

    </StackLayout>
</ContentPage>

Das Problem besteht darin, dass Datum und Uhrzeit einmal festgelegt werden, wenn die Seite zum ersten Mal erstellt wird, und sich nie ändern:

Ansicht Anzeigen von Datum und Uhrzeit

Eine XAML-Datei kann eine Uhr anzeigen, die immer die aktuelle Uhrzeit anzeigt, benötigt jedoch Code, um zu helfen. Wenn Sie im Sinne von MVVM denken, sind Model und ViewModel Klassen, die vollständig im Code geschrieben sind. Die Ansicht ist häufig eine XAML-Datei, die über Datenbindungen auf im ViewModel definierte Eigenschaften verweist.

Ein richtiges Modell ist unwissend gegenüber dem ViewModel, und ein richtiges ViewModel ist unwissend gegenüber der Ansicht. Allerdings passt ein Programmierer häufig die Datentypen an, die vom ViewModel verfügbar gemacht werden, an die Datentypen, die bestimmten Benutzeroberflächen zugeordnet sind. Wenn beispielsweise ein Modell auf eine Datenbank zugreift, die 8-Bit-ASCII-Zeichenfolgen enthält, muss ViewModel zwischen diesen Zeichenfolgen in Unicode-Zeichenfolgen konvertieren, um die ausschließliche Verwendung von Unicode auf der Benutzeroberfläche zu ermöglichen.

In einfachen MVVM-Beispielen (wie die hier gezeigten) gibt es häufig überhaupt kein Modell, und das Muster umfasst nur ein View und ViewModel, das mit Datenbindungen verknüpft ist.

Hier ist ein ViewModel für eine Uhr mit nur einer einzelnen Eigenschaft namens DateTime, die diese DateTime Eigenschaft jede Sekunde aktualisiert:

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    class ClockViewModel : INotifyPropertyChanged
    {
        DateTime dateTime;

        public event PropertyChangedEventHandler PropertyChanged;

        public ClockViewModel()
        {
            this.DateTime = DateTime.Now;

            Device.StartTimer(TimeSpan.FromSeconds(1), () =>
                {
                    this.DateTime = DateTime.Now;
                    return true;
                });
        }

        public DateTime DateTime
        {
            set
            {
                if (dateTime != value)
                {
                    dateTime = value;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
                    }
                }
            }
            get
            {
                return dateTime;
            }
        }
    }
}

ViewModels implementieren im Allgemeinen die INotifyPropertyChanged -Schnittstelle. Dies bedeutet, dass die -Klasse jedes Mal ein PropertyChanged Ereignis auslöst, wenn sich eine ihrer Eigenschaften ändert. Der Datenbindungsmechanismus in Xamarin.Forms fügt einen Handler an dieses PropertyChanged Ereignis an, sodass es benachrichtigt werden kann, wenn sich eine Eigenschaft ändert, und das Ziel mit dem neuen Wert aktualisiert wird.

Eine Uhr, die auf diesem ViewModel basiert, kann so einfach wie folgt sein:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ClockPage"
             Title="Clock Page">

    <Label Text="{Binding DateTime, StringFormat='{0:T}'}"
           FontSize="Large"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <Label.BindingContext>
            <local:ClockViewModel />
        </Label.BindingContext>
    </Label>
</ContentPage>

Beachten Sie, dass auf ClockViewModel die BindingContext der Label verwendenden Eigenschaftselementtags festgelegt ist. Alternativ können Sie die ClockViewModel in einer Resources Auflistung instanziieren und über eine StaticResource Markuperweiterung auf festlegenBindingContext. Oder die CodeBehind-Datei kann das ViewModel instanziieren.

Die Binding Markuperweiterung für die Text -Eigenschaft der Label formatiert die DateTime -Eigenschaft. Dies ist die Anzeige:

Ansicht Anzeigen von Datum und Uhrzeit über ViewModel

Es ist auch möglich, auf einzelne Eigenschaften der DateTime Eigenschaft des ViewModel zuzugreifen, indem die Eigenschaften durch Punkte getrennt werden:

<Label Text="{Binding DateTime.Second, StringFormat='{0}'}" … >

Interaktives MVVM

MVVM wird häufig mit bidirektionalen Datenbindungen für eine interaktive Ansicht verwendet, die auf einem zugrunde liegenden Datenmodell basiert.

Im Folgenden sehen Sie eine Klasse namens HslViewModel , die einen Color Wert in Hue, Saturation- und Luminosity -Werte und umgekehrt konvertiert:

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    public class HslViewModel : INotifyPropertyChanged
    {
        double hue, saturation, luminosity;
        Color color;

        public event PropertyChangedEventHandler PropertyChanged;

        public double Hue
        {
            set
            {
                if (hue != value)
                {
                    hue = value;
                    OnPropertyChanged("Hue");
                    SetNewColor();
                }
            }
            get
            {
                return hue;
            }
        }

        public double Saturation
        {
            set
            {
                if (saturation != value)
                {
                    saturation = value;
                    OnPropertyChanged("Saturation");
                    SetNewColor();
                }
            }
            get
            {
                return saturation;
            }
        }

        public double Luminosity
        {
            set
            {
                if (luminosity != value)
                {
                    luminosity = value;
                    OnPropertyChanged("Luminosity");
                    SetNewColor();
                }
            }
            get
            {
                return luminosity;
            }
        }

        public Color Color
        {
            set
            {
                if (color != value)
                {
                    color = value;
                    OnPropertyChanged("Color");

                    Hue = value.Hue;
                    Saturation = value.Saturation;
                    Luminosity = value.Luminosity;
                }
            }
            get
            {
                return color;
            }
        }

        void SetNewColor()
        {
            Color = Color.FromHsla(Hue, Saturation, Luminosity);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Änderungen an den HueEigenschaften , Saturationund Luminosity bewirken, dass sich die Color Eigenschaft ändert, und Änderungen an Color bewirken, dass sich die anderen drei Eigenschaften ändern. Dies kann wie eine Endlosschleife erscheinen, mit der Ausnahme, dass die -Klasse das PropertyChanged -Ereignis erst aufruft, wenn sich die Eigenschaft geändert hat. Dadurch wird die ansonsten nicht kontrollierbare Feedbackschleife beendet.

Die folgende XAML-Datei enthält eine BoxView , deren Color Eigenschaft an die Color Eigenschaft des ViewModel gebunden ist, und drei Slider und drei Label Ansichten, die an die HueEigenschaften , Saturationund Luminosity gebunden sind:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.HslColorScrollPage"
             Title="HSL Color Scroll Page">
    <ContentPage.BindingContext>
        <local:HslViewModel Color="Aqua" />
    </ContentPage.BindingContext>

    <StackLayout Padding="10, 0">
        <BoxView Color="{Binding Color}"
                 VerticalOptions="FillAndExpand" />

        <Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}"
               HorizontalOptions="Center" />

        <Slider Value="{Binding Hue, Mode=TwoWay}" />

        <Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}"
               HorizontalOptions="Center" />

        <Slider Value="{Binding Saturation, Mode=TwoWay}" />

        <Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}"
               HorizontalOptions="Center" />

        <Slider Value="{Binding Luminosity, Mode=TwoWay}" />
    </StackLayout>
</ContentPage>

Die Bindung für jedes Label ist die Standardbindung OneWay. Es muss nur der Wert angezeigt werden. Die Bindung für jedes Slider ist TwoWayjedoch . Dadurch kann die Slider aus viewModel initialisiert werden. Beachten Sie, dass die Color -Eigenschaft auf Aqua festgelegt ist, wenn viewModel instanziiert wird. Eine Änderung in muss Slider jedoch auch einen neuen Wert für die Eigenschaft im ViewModel festlegen, das dann eine neue Farbe berechnet.

MVVM mit Two-Way Datenbindungen

Befehlen mit ViewModels

In vielen Fällen ist das MVVM-Muster auf die Bearbeitung von Datenelementen beschränkt: Benutzeroberflächenobjekte in der Ansicht parallele Datenobjekte in ViewModel.

Manchmal muss die Ansicht jedoch Schaltflächen enthalten, die verschiedene Aktionen im ViewModel auslösen. Das ViewModel darf jedoch keine Handler für die Schaltflächen enthalten Clicked , da dies das ViewModel an ein bestimmtes Benutzeroberflächenparadigma binden würde.

Damit ViewModels unabhängiger von bestimmten Benutzeroberflächenobjekten sein kann, aber trotzdem Methoden innerhalb des ViewModel aufgerufen werden können, ist eine Befehlsschnittstelle vorhanden. Diese Befehlsschnittstelle wird von den folgenden Elementen in Xamarin.Formsunterstützt:

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (und damit auch ImageCell)
  • ListView
  • TapGestureRecognizer

Mit Ausnahme des SearchBar - und ListView -Elements definieren diese Elemente zwei Eigenschaften:

  • Command vom Typ System.Windows.Input.ICommand
  • CommandParameter vom Typ Object

Definiert SearchBarSearchCommand die Eigenschaften undSearchCommandParameter, während eine ListView Eigenschaft vom Typ ICommanddefiniert RefreshCommand wird.

Die ICommand -Schnittstelle definiert zwei Methoden und ein Ereignis:

  • void Execute(object arg)
  • bool CanExecute(object arg)
  • event EventHandler CanExecuteChanged

Das ViewModel kann Eigenschaften des Typs ICommanddefinieren. Sie können diese Eigenschaften dann an die Command -Eigenschaft jedes Button oder eines anderen Elements oder vielleicht an eine benutzerdefinierte Ansicht binden, die diese Schnittstelle implementiert. Optional können Sie die CommandParameter -Eigenschaft festlegen, um einzelne Button Objekte (oder andere Elemente) zu identifizieren, die an diese ViewModel-Eigenschaft gebunden sind. Intern ruft die Button - Execute Methode auf, wenn der Benutzer auf tippt, Buttonund an die Execute -Methode CommandParameterübergeben.

Die CanExecute Methode und CanExecuteChanged das Ereignis werden für Fälle verwendet, in denen ein Button Tippen derzeit ungültig sein kann. In diesem Fall sollte sich das Button selbst deaktivieren. Der Button ruft auf CanExecute , wenn die Command -Eigenschaft zum ersten Mal festgelegt wird und wann immer das CanExecuteChanged Ereignis ausgelöst wird. Wenn CanExecute zurückgibt false, deaktiviert sich der Button selbst und generiert Execute keine Aufrufe.

Um Hilfe beim Hinzufügen von Befehlen zu Ihren ViewModels zu benötigen, definiert zwei Klassen, Xamarin.Forms die implementieren ICommand: Command und Command<T> wobei T der Typ der Argumente für Execute und CanExecuteist. Diese beiden Klassen definieren mehrere Konstruktoren sowie eine ChangeCanExecute Methode, die viewModel aufrufen kann, um das Auslösen des Ereignisses durch das Command Objekt zu erzwingen CanExecuteChanged .

Hier ist ein ViewModel für eine einfache Tastatur, die zum Eingeben von Telefonnummern vorgesehen ist. Beachten Sie, dass die - und CanExecute -ExecuteMethode direkt im Konstruktor als Lambdafunktionen definiert sind:

using System;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamlSamples
{
    class KeypadViewModel : INotifyPropertyChanged
    {
        string inputString = "";
        string displayText = "";
        char[] specialChars = { '*', '#' };

        public event PropertyChangedEventHandler PropertyChanged;

        // Constructor
        public KeypadViewModel()
        {
            AddCharCommand = new Command<string>((key) =>
                {
                    // Add the key to the input string.
                    InputString += key;
                });

            DeleteCharCommand = new Command(() =>
                {
                    // Strip a character from the input string.
                    InputString = InputString.Substring(0, InputString.Length - 1);
                },
                () =>
                {
                    // Return true if there's something to delete.
                    return InputString.Length > 0;
                });
        }

        // Public properties
        public string InputString
        {
            protected set
            {
                if (inputString != value)
                {
                    inputString = value;
                    OnPropertyChanged("InputString");
                    DisplayText = FormatText(inputString);

                    // Perhaps the delete button must be enabled/disabled.
                    ((Command)DeleteCharCommand).ChangeCanExecute();
                }
            }

            get { return inputString; }
        }

        public string DisplayText
        {
            protected set
            {
                if (displayText != value)
                {
                    displayText = value;
                    OnPropertyChanged("DisplayText");
                }
            }
            get { return displayText; }
        }

        // ICommand implementations
        public ICommand AddCharCommand { protected set; get; }

        public ICommand DeleteCharCommand { protected set; get; }

        string FormatText(string str)
        {
            bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
            string formatted = str;

            if (hasNonNumbers || str.Length < 4 || str.Length > 10)
            {
            }
            else if (str.Length < 8)
            {
                formatted = String.Format("{0}-{1}",
                                          str.Substring(0, 3),
                                          str.Substring(3));
            }
            else
            {
                formatted = String.Format("({0}) {1}-{2}",
                                          str.Substring(0, 3),
                                          str.Substring(3, 3),
                                          str.Substring(6));
            }
            return formatted;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Dieses ViewModel geht davon aus, dass die AddCharCommand Eigenschaft an die Command -Eigenschaft mehrerer Schaltflächen gebunden ist (oder an ein anderes Element, das über eine Befehlsschnittstelle verfügt), von denen jede durch CommandParameteridentifiziert wird. Diese Schaltflächen fügen einer InputString Eigenschaft Zeichen hinzu, die dann als Telefonnummer für die DisplayText Eigenschaft formatiert wird.

Es gibt auch eine zweite Eigenschaft vom Typ ICommand namens DeleteCharCommand. Dies ist an eine Rückabstandsschaltfläche gebunden, aber die Schaltfläche sollte deaktiviert werden, wenn keine Zeichen zum Löschen vorhanden sind.

Die folgende Tastatur ist visuell nicht so anspruchsvoll, wie sie sein könnte. Stattdessen wurde das Markup auf ein Minimum reduziert, um die Verwendung der Befehlsschnittstelle deutlicher zu veranschaulichen:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.KeypadPage"
             Title="Keypad Page">

    <Grid HorizontalOptions="Center"
          VerticalOptions="Center">
        <Grid.BindingContext>
            <local:KeypadViewModel />
        </Grid.BindingContext>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80" />
            <ColumnDefinition Width="80" />
            <ColumnDefinition Width="80" />
        </Grid.ColumnDefinitions>

        <!-- Internal Grid for top row of items -->
        <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Frame Grid.Column="0"
                   OutlineColor="Accent">
                <Label Text="{Binding DisplayText}" />
            </Frame>

            <Button Text="&#x21E6;"
                    Command="{Binding DeleteCharCommand}"
                    Grid.Column="1"
                    BorderWidth="0" />
        </Grid>

        <Button Text="1"
                Command="{Binding AddCharCommand}"
                CommandParameter="1"
                Grid.Row="1" Grid.Column="0" />

        <Button Text="2"
                Command="{Binding AddCharCommand}"
                CommandParameter="2"
                Grid.Row="1" Grid.Column="1" />

        <Button Text="3"
                Command="{Binding AddCharCommand}"
                CommandParameter="3"
                Grid.Row="1" Grid.Column="2" />

        <Button Text="4"
                Command="{Binding AddCharCommand}"
                CommandParameter="4"
                Grid.Row="2" Grid.Column="0" />

        <Button Text="5"
                Command="{Binding AddCharCommand}"
                CommandParameter="5"
                Grid.Row="2" Grid.Column="1" />

        <Button Text="6"
                Command="{Binding AddCharCommand}"
                CommandParameter="6"
                Grid.Row="2" Grid.Column="2" />

        <Button Text="7"
                Command="{Binding AddCharCommand}"
                CommandParameter="7"
                Grid.Row="3" Grid.Column="0" />

        <Button Text="8"
                Command="{Binding AddCharCommand}"
                CommandParameter="8"
                Grid.Row="3" Grid.Column="1" />

        <Button Text="9"
                Command="{Binding AddCharCommand}"
                CommandParameter="9"
                Grid.Row="3" Grid.Column="2" />

        <Button Text="*"
                Command="{Binding AddCharCommand}"
                CommandParameter="*"
                Grid.Row="4" Grid.Column="0" />

        <Button Text="0"
                Command="{Binding AddCharCommand}"
                CommandParameter="0"
                Grid.Row="4" Grid.Column="1" />

        <Button Text="#"
                Command="{Binding AddCharCommand}"
                CommandParameter="#"
                Grid.Row="4" Grid.Column="2" />
    </Grid>
</ContentPage>

Die Command -Eigenschaft der ersten Button , die in diesem Markup angezeigt wird, ist an das DeleteCharCommandgebunden. Der Rest ist an das AddCharCommand mit einem CommandParameter gebunden, das dem Zeichen entspricht, das auf dem Button Gesicht angezeigt wird. Hier ist das Programm in Aktion:

Befehlsrechner mit MVVM und Befehlen

Aufrufen asynchroner Methoden

Befehle können auch asynchrone Methoden aufrufen. Dies wird erreicht, indem beim Angeben Execute der -Methode die async Schlüsselwörter und await verwendet werden:

DownloadCommand = new Command (async () => await DownloadAsync ());

Dies gibt an, dass die DownloadAsync -Methode eine Task ist und erwartet werden sollte:

async Task DownloadAsync ()
{
    await Task.Run (() => Download ());
}

void Download ()
{
    ...
}

Implementieren eines Navigationsmenüs

Das XamlSamples-Programm , das den gesamten Quellcode in dieser Artikelreihe enthält, verwendet ein ViewModel für seine Startseite. Dieses ViewModel ist eine Definition einer kurzen Klasse mit drei Eigenschaften mit dem Namen Type, Titleund Description die den Typ der einzelnen Beispielseiten, einen Titel und eine kurze Beschreibung enthalten. Darüber hinaus definiert ViewModel eine statische Eigenschaft namens All , die eine Auflistung aller Seiten im Programm ist:

public class PageDataViewModel
{
    public PageDataViewModel(Type type, string title, string description)
    {
        Type = type;
        Title = title;
        Description = description;
    }

    public Type Type { private set; get; }

    public string Title { private set; get; }

    public string Description { private set; get; }

    static PageDataViewModel()
    {
        All = new List<PageDataViewModel>
        {
            // Part 1. Getting Started with XAML
            new PageDataViewModel(typeof(HelloXamlPage), "Hello, XAML",
                                  "Display a Label with many properties set"),

            new PageDataViewModel(typeof(XamlPlusCodePage), "XAML + Code",
                                  "Interact with a Slider and Button"),

            // Part 2. Essential XAML Syntax
            new PageDataViewModel(typeof(GridDemoPage), "Grid Demo",
                                  "Explore XAML syntax with the Grid"),

            new PageDataViewModel(typeof(AbsoluteDemoPage), "Absolute Demo",
                                  "Explore XAML syntax with AbsoluteLayout"),

            // Part 3. XAML Markup Extensions
            new PageDataViewModel(typeof(SharedResourcesPage), "Shared Resources",
                                  "Using resource dictionaries to share resources"),

            new PageDataViewModel(typeof(StaticConstantsPage), "Static Constants",
                                  "Using the x:Static markup extensions"),

            new PageDataViewModel(typeof(RelativeLayoutPage), "Relative Layout",
                                  "Explore XAML markup extensions"),

            // Part 4. Data Binding Basics
            new PageDataViewModel(typeof(SliderBindingsPage), "Slider Bindings",
                                  "Bind properties of two views on the page"),

            new PageDataViewModel(typeof(SliderTransformsPage), "Slider Transforms",
                                  "Use Sliders with reverse bindings"),

            new PageDataViewModel(typeof(ListViewDemoPage), "ListView Demo",
                                  "Use a ListView with data bindings"),

            // Part 5. From Data Bindings to MVVM
            new PageDataViewModel(typeof(OneShotDateTimePage), "One-Shot DateTime",
                                  "Obtain the current DateTime and display it"),

            new PageDataViewModel(typeof(ClockPage), "Clock",
                                  "Dynamically display the current time"),

            new PageDataViewModel(typeof(HslColorScrollPage), "HSL Color Scroll",
                                  "Use a view model to select HSL colors"),

            new PageDataViewModel(typeof(KeypadPage), "Keypad",
                                  "Use a view model for numeric keypad logic")
        };
    }

    public static IList<PageDataViewModel> All { private set; get; }
}

Die XAML-Datei für MainPage definiert eine ListBox , deren ItemsSource Eigenschaft auf diese All Eigenschaft festgelegt ist und die eine TextCell zum Anzeigen der Title Eigenschaften und Description jeder Seite enthält:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.MainPage"
             Padding="5, 0"
             Title="XAML Samples">

    <ListView ItemsSource="{x:Static local:PageDataViewModel.All}"
              ItemSelected="OnListViewItemSelected">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding Title}"
                          Detail="{Binding Description}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

Die Seiten werden in einer scrollbaren Liste angezeigt:

Scrollbare Liste von Seiten

Der Handler in der CodeBehind-Datei wird ausgelöst, wenn der Benutzer ein Element auswählt. Der Handler legt die SelectedItem -Eigenschaft des ListBox back auf null fest, instanziiert dann die ausgewählte Seite und navigiert zu dieser:

private async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    (sender as ListView).SelectedItem = null;

    if (args.SelectedItem != null)
    {
        PageDataViewModel pageData = args.SelectedItem as PageDataViewModel;
        Page page = (Page)Activator.CreateInstance(pageData.Type);
        await Navigation.PushAsync(page);
    }
}

Video

Xamarin Evolve 2016: MVVM einfach gemacht mit Xamarin.Forms und Prism

Zusammenfassung

XAML ist ein leistungsstarkes Tool zum Definieren von Benutzeroberflächen in Xamarin.Forms Anwendungen, insbesondere wenn Datenbindung und MVVM verwendet werden. Das Ergebnis ist eine sauber, elegante und potenziell werkzeugfähige Darstellung einer Benutzeroberfläche mit der gesamten Hintergrundunterstützung im Code.

Auf Channel 9 und auf YouTube finden Sie weitere Videos zu Xamarin.