Teil 5. Von Datenbindungen zu MVVM

Das Architekturmuster Model-View-ViewModel (MVVM) wurde mit XAML erfunden. Das Muster erzwingt eine Trennung zwischen drei Softwareebenen – der XAML-Benutzeroberfläche, die als Ansicht bezeichnet wird; die zugrunde liegenden Daten, die als Modell bezeichnet werden; und eine Zwischenschaltung zwischen der Ansicht und dem Modell, die als ViewModel bezeichnet wird. Die Ansicht und das ViewModel sind häufig über datenbindungen verbunden, die in der XAML-Datei definiert sind. Der BindingContext für die Ansicht ist in der Regel eine Instanz des ViewModel.

Einfaches ViewModel

Als Einführung in ViewModels betrachten wir zunächst ein Programm ohne ein Programm. Weiter oben haben Sie gesehen, wie Sie eine neue XML-Namespacedeklaration definieren, damit eine XAML-Datei auf Klassen in anderen Assemblys verweist. Hier ist 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 aus der statischen DateTime.Now Eigenschaft abzurufen und diesen DateTime Wert auf ein StackLayout:BindingContext

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

BindingContext ist eine spezielle Eigenschaft: Wenn Sie das BindingContext Element für ein Element festlegen, wird es von allen untergeordneten Elementen dieses Elements geerbt. Dies bedeutet, dass alle untergeordneten Elemente StackLayout dieses Objekts gleich BindingContextsind und sie 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, die scheinbar einen Bindungspfad fehlen. Dies bedeutet, dass der Wert selbst für folgendes DateTimeStringFormatverwendet wird:

<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 das Datum und die Uhrzeit einmal festgelegt werden, wenn die Seite zum ersten Mal erstellt wird und sich nie ändern:

Anzeigen von Datum und Uhrzeit

Eine XAML-Datei kann eine Uhr anzeigen, die immer die aktuelle Uhrzeit anzeigt, benötigt jedoch Code, um hilfe zu erhalten. Beim Denken in Bezug auf MVVM sind Modell und ViewModel Klassen, die vollständig im Code geschrieben werden. Die Ansicht ist häufig eine XAML-Datei, die auf eigenschaften verweist, die im ViewModel über Datenbindungen definiert sind.

Ein richtiges Modell ist unwissend vom ViewModel, und ein ordnungsgemäßes ViewModel ist unwissend von der Ansicht. Ein Programmierer passt jedoch häufig die Datentypen an, die vom ViewModel auf die Datentypen zugeschnitten sind, die bestimmten Benutzeroberflächen zugeordnet sind. Wenn beispielsweise ein Modell auf eine Datenbank zugreift, die 8-Bit-ASCII-Zeichenfolgen enthält, muss das ViewModel zwischen diesen Zeichenfolgen in Unicode-Zeichenfolgen konvertieren, um die ausschließliche Verwendung von Unicode auf der Benutzeroberfläche zu berücksichtigen.

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

Hier ist ein ViewModel für eine Uhr mit nur einer einzigen 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 in der Regel die INotifyPropertyChanged Schnittstelle, was bedeutet, dass die Klasse ein PropertyChanged Ereignis auslöst, wenn eine seiner Eigenschaften geändert wird. Der Datenbindungsmechanismus fügt Xamarin.Forms einen Handler an dieses PropertyChanged Ereignis an, sodass er 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, wie die ClockViewModel Einstellung auf die BindingContextLabel verwendung von Eigenschaftselementtags festgelegt ist. Alternativ können Sie die ClockViewModel In einer Resources Sammlung instanziieren und auf die BindingContext über eine StaticResource Markuperweiterung festlegen. Oder die CodeBehind-Datei kann das ViewModel instanziieren.

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

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}'}" … >

Interaktive MVVM

MVVM wird häufig mit bidirektionale Datenbindungen für eine interaktive Ansicht basierend auf einem zugrunde liegenden Datenmodell verwendet.

Hier ist eine Klasse mit dem Namen HslViewModel , die einen Color Wert in Hue, Saturationund Luminosity Werte konvertiert und umgekehrt:

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 Hue, Saturationund Luminosity Eigenschaften führen dazu, dass sich die Color Eigenschaft ändert, und Änderungen Color bewirken, dass sich die anderen drei Eigenschaften ändern. Dies mag wie eine Endlosschleife aussehen, mit der Ausnahme, dass die Klasse das PropertyChanged Ereignis nicht aufruft, es sei denn, die Eigenschaft wurde geändert. Dadurch wird die andernfalls unkontrollierbare Feedbackschleife beendet.

Die folgende XAML-Datei enthält eine BoxView Eigenschaft, deren Color Eigenschaft an die Color Eigenschaft des ViewModel gebunden ist, und drei Slider und drei Label Ansichten, die an die HueEigenschaften und SaturationLuminosity Eigenschaften 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 an jede Label ist Standard OneWay. Der Wert muss nur angezeigt werden. Aber die Bindung an jedem Slider ist TwoWay. Dadurch kann das Slider ViewModel initialisiert werden. Beachten Sie, dass die Color Eigenschaft festgelegt Aqua ist, wenn das ViewModel instanziiert wird. Eine Änderung der Slider Eigenschaft muss jedoch auch einen neuen Wert für die Eigenschaft im ViewModel festlegen, wodurch dann eine neue Farbe berechnet wird.

MVVM mit Bidirektionale Datenbindungen

Befehle mit ViewModels

In vielen Fällen ist das MVVM-Muster auf die Manipulation von Datenelementen beschränkt: Benutzeroberflächenobjekte in den View parallelen Datenobjekten im 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 das ViewModel an ein bestimmtes Benutzeroberflächenparadigma gebunden würde.

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

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

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

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

Die SearchBar Definierten SearchCommand und SearchCommandParameter Eigenschaften, während die ListView Eigenschaft vom RefreshCommand Typ ICommanddefiniert 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 vom Typ ICommanddefinieren. Sie können diese Eigenschaften dann an die Command-Eigenschaft jedes Button-Elements oder anderen Elements binden, oder vielleicht an eine benutzerdefinierte Ansicht, die diese Schnittstelle implementiert. Sie können optional die CommandParameter Eigenschaft festlegen, um einzelne Button Objekte (oder andere Elemente) zu identifizieren, die an diese ViewModel-Eigenschaft gebunden sind. Intern ruft Button die Methode Execute auf, wenn der Benutzer auf Button tippt, und übergibt der Methode Execute an sein CommandParameter.

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

Um Hilfe beim Hinzufügen von Befehlen zu Ihren ViewModels zu ermöglichen, definieren Sie zwei Klassen, Xamarin.Forms die folgendes implementieren ICommand: Command und Command<T> wo T befindet sich der Typ der Argumente für Execute und CanExecute. Diese beiden Klassen definieren mehrere Konstruktoren sowie eine ChangeCanExecute Methode, die das ViewModel aufrufen kann, um das Objekt zum Auslösen des CommandCanExecuteChanged Ereignisses zu erzwingen.

Hier ist ein ViewModel für eine einfache Wähltastatur, die für die Eingabe von Telefonnummern vorgesehen ist. Beachten Sie, dass die und CanExecute die Execute Methode direkt im Konstruktor als Lambda-Funktionen 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 Eigenschaft an die AddCharCommandCommand Eigenschaft mehrerer Schaltflächen gebunden ist (oder etwas anderes, das über eine Befehlsschnittstelle verfügt), von denen jede durch die CommandParameter. Diese Tasten fügen Zeichen zu einer InputString-Eigenschaft hinzu, die dann als Telefonnummer für die DisplayText-Eigenschaft formatiert wird.

Es gibt auch eine zweite Eigenschaft des Typs ICommand namens DeleteCharCommand. Dies ist an eine Rücktasten-Schaltfläche gebunden, aber die Schaltfläche sollte deaktiviert werden, wenn keine Zeichen zu löschen sind.

Die folgende Wähltastatur ist nicht so visuell anspruchsvoll wie es 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 des ersten Button , das in diesem Markup angezeigt wird, ist an das DeleteCharCommandElement gebunden; der Rest ist mit AddCharCommand dem CommandParameter Zeichen verbunden, das im Button Gesicht angezeigt wird. Hier ist das Programm in Aktion:

Rechner mit MVVM und Befehlen

Aufrufen asynchroner Methoden

Befehle können auch asynchrone Methoden aufrufen. Dies wird mithilfe der async und await Schlüsselwort (keyword) erreicht, wenn Sie die Execute Methode angeben:

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

Dies gibt an, dass es sich bei der DownloadAsync Methode um eine Task und es sollte gewartet werden:

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

void Download ()
{
    ...
}

Implementieren eines Navigationsmenüs

Das Beispielprogramm, das den gesamten Quellcode in dieser Artikelreihe enthält, verwendet ein ViewModel für seine Startseite. This ViewModel is a definition of a short class with three properties named Type, , Titleand Description that contain the type of each of the sample pages, a title, and a short description. Darüber hinaus definiert das ViewModel eine statische Eigenschaft mit dem Namen 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 Eigenschaft, deren ItemsSource Eigenschaft auf diese All Eigenschaft festgelegt ist und eine TextCell für die Anzeige der Title einzelnen Seiten und Description Eigenschaften 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 bildlauffähigen Liste angezeigt:

Bildlauffähige 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 der ListBox Zurück auf null und instanziiert dann die ausgewählte Seite und navigiert zu der Seite:

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 Made Simple with Xamarin.Forms and Prism

Zusammenfassung

XAML ist ein leistungsfähiges 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 toolierbare Darstellung einer Benutzeroberfläche mit allen Hintergrundunterstützungen im Code.

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