Udostępnij za pośrednictwem


Część 5. Od powiązań danych do wzorca MVVM

Wzorzec architektury Model-View-ViewModel (MVVM) został wynaleziony z myślą o języku XAML. Wzorzec wymusza separację między trzema warstwami oprogramowania — interfejsem użytkownika XAML nazywanym widokiem; danych bazowych, nazywanych modelem; i pośrednik między widokiem a modelem, nazywanym Modelem ViewModel. Model View i ViewModel są często połączone za pośrednictwem powiązań danych zdefiniowanych w pliku XAML. Obiekt BindingContext dla widoku jest zwykle wystąpieniem modelu ViewModel.

Prosty model ViewModel

Jako wprowadzenie do modelu ViewModels najpierw przyjrzyjmy się programowi bez jednego. Wcześniej pokazano, jak zdefiniować nową deklarację przestrzeni nazw XML, aby umożliwić plikowi XAML odwoływanie się do klas w innych zestawach. Oto program, który definiuje deklarację przestrzeni nazw XML dla System przestrzeni nazw:

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

Program może użyć x:Static polecenia , aby uzyskać bieżącą datę i godzinę z właściwości statycznej DateTime.Now i ustawić BindingContext wartość DateTime na wartość w elemencie StackLayout:

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

BindingContext jest właściwością specjalną: po ustawieniu BindingContext elementu na element jest dziedziczony przez wszystkie elementy podrzędne tego elementu. Oznacza to, że wszystkie elementy podrzędne obiektu StackLayout mają ten sam BindingContextobiekt i mogą zawierać proste powiązania z właściwościami tego obiektu.

W programie One-Shot DateTime dwa elementy podrzędne zawierają powiązania z właściwościami tej DateTime wartości, ale dwie inne elementy podrzędne zawierają powiązania, które wydają się brakować ścieżki powiązania. Oznacza to, że DateTime sama wartość jest używana dla elementu StringFormat:

<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>

Problem polega na tym, że data i godzina są ustawiane raz po utworzeniu strony i nigdy nie zmieniają się:

Wyświetlanie daty i godziny

Plik XAML może wyświetlać zegar, który zawsze pokazuje bieżący czas, ale potrzebuje kodu, aby pomóc. Podczas myślenia w kategoriach MVVM model i ViewModel są klasami napisanymi w całości w kodzie. Widok jest często plikiem XAML, który odwołuje się do właściwości zdefiniowanych w modelu ViewModel za pomocą powiązań danych.

Odpowiedni model jest ignorowany przez model ViewModel, a odpowiedni model ViewModel jest ignorowany w widoku. Jednak często programista dostosowuje typy danych uwidocznione przez model ViewModel do typów danych skojarzonych z określonymi interfejsami użytkownika. Jeśli na przykład model uzyskuje dostęp do bazy danych zawierającej 8-bitowe ciągi ASCII, model ViewModel będzie musiał przekonwertować między tymi ciągami na ciągi Unicode, aby uwzględnić wyłączne użycie unicode w interfejsie użytkownika.

W prostych przykładach maszyny MVVM (takich jak pokazano tutaj), często nie ma modelu, a wzorzec obejmuje tylko model View i ViewModel połączony z powiązaniami danych.

Oto model ViewModel dla zegara z tylko jedną właściwością o nazwie DateTime, która aktualizuje właściwość DateTime co sekundę:

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;
            }
        }
    }
}

Modely ViewModel zwykle implementują INotifyPropertyChanged interfejs, co oznacza, że klasa uruchamia PropertyChanged zdarzenie za każdym razem, gdy jedna z jego właściwości ulegnie zmianie. Mechanizm powiązania danych w Xamarin.Forms programie dołącza procedurę obsługi do tego PropertyChanged zdarzenia, aby można było otrzymywać powiadomienia o zmianie właściwości i aktualizowaniu obiektu docelowego przy użyciu nowej wartości.

Zegar oparty na tym modelu ViewModel może być tak prosty, jak w następujący sposób:

<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>

Zwróć uwagę, ClockViewModel że właściwość jest ustawiona na LabelBindingContext znaczniki elementu using. Alternatywnie możesz utworzyć wystąpienie ClockViewModel obiektu w Resources kolekcji i ustawić je na BindingContext za pomocą StaticResource rozszerzenia znaczników. Możesz też utworzyć wystąpienie pliku z kodem w modelu ViewModel.

Binding Rozszerzenie znaczników we Text właściwości Label formatuje DateTime właściwość . Oto ekran:

Wyświetlanie daty i godziny za pośrednictwem modelu ViewModel

Można również uzyskać dostęp do poszczególnych właściwości DateTime właściwości modelu ViewModel, oddzielając właściwości kropkami:

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

Interaktywna maszyna MVVM

Maszyna MVVM jest często używana z dwukierunkowymi powiązaniami danych dla interaktywnego widoku opartego na bazowym modelu danych.

Oto klasa o nazwie HslViewModel , która konwertuje Color wartość na Huewartości , Saturationi Luminosity i na odwrót:

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));
        }
    }
}

HueZmiany właściwości , Saturationi Luminosity powodują zmianę Color właściwości, a zmiany Color powodują zmianę pozostałych trzech właściwości. Może to wydawać się nieskończoną pętlą, z tą różnicą, że klasa nie wywołuje PropertyChanged zdarzenia, chyba że właściwość uległa zmianie. Spowoduje to zakończenie niekontrolowanej pętli opinii.

Poniższy plik XAML zawiera BoxView właściwość, której Color właściwość jest powiązana z właściwością Color ViewModel, oraz trzema Slider i trzema Label widokami powiązanymi z właściwościami Hue, Saturationi Luminosity :

<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>

Powiązanie dla każdego Label z nich jest ustawieniem domyślnym OneWay. Musi tylko wyświetlić wartość. Jednak powiązanie dla każdego Slider z nich to TwoWay. Umożliwia to zainicjowanie elementu Slider z modelu ViewModel. Zwróć uwagę, że Color właściwość jest ustawiona na Aqua wartość , gdy wystąpienie modelu ViewModel jest tworzone. Jednak zmiana Slider w obiekcie musi również ustawić nową wartość właściwości w modelu ViewModel, która następnie oblicza nowy kolor.

MvVM przy użyciu powiązań danych dwukierunkowych

Commanding with ViewModels

W wielu przypadkach wzorzec MVVM jest ograniczony do manipulowania elementami danych: obiekty interfejsu użytkownika w widoku obiektów danych równoległych w modelu ViewModel.

Jednak czasami widok musi zawierać przyciski, które wyzwalają różne akcje w modelu ViewModel. Jednak model ViewModel nie może zawierać Clicked procedur obsługi przycisków, ponieważ spowoduje to powiązanie modelu ViewModel z określonym paradygmatem interfejsu użytkownika.

Aby umożliwić modelom ViewModel bardziej niezależne od określonych obiektów interfejsu użytkownika, ale nadal zezwalaj na wywoływanie metod w modelu ViewModel, istnieje interfejs polecenia . Ten interfejs polecenia jest obsługiwany przez następujące elementy w pliku Xamarin.Forms:

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (i dlatego też ImageCell)
  • ListView
  • TapGestureRecognizer

Z wyjątkiem elementu SearchBar i ListView te elementy definiują dwie właściwości:

  • Command typu System.Windows.Input.ICommand
  • CommandParameter typu Object

Definiuje SearchBar właściwości i SearchCommandParameter , podczas gdy ListView definiuje RefreshCommand właściwość typu ICommand.SearchCommand

Interfejs ICommand definiuje dwie metody i jedno zdarzenie:

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

Model ViewModel może definiować właściwości typu ICommand. Następnie można powiązać te właściwości z właściwością Command każdego Button lub innego elementu, a może z widokiem niestandardowym, który implementuje ten interfejs. Opcjonalnie można ustawić CommandParameter właściwość, aby zidentyfikować poszczególne Button obiekty (lub inne elementy), które są powiązane z tą właściwością ViewModel. Wewnętrznie metoda wywołuje metodę za każdym razem, Button gdy użytkownik naciśnie metodę Button, przekazując ją do Execute metody CommandParameter.Execute

Metoda CanExecute i CanExecuteChanged zdarzenie są używane w przypadkach, Button gdy naciśnięcie może być obecnie nieprawidłowe, w takim przypadku Button polecenie powinno zostać wyłączone. Wywołania ButtonCanExecute , gdy Command właściwość jest ustawiana po raz pierwszy i za każdym razem, CanExecuteChanged gdy zdarzenie jest uruchamiane. Jeśli CanExecute funkcja zwraca falsewartość , Button funkcja wyłącza się i nie generuje Execute wywołań.

Aby uzyskać pomoc dotyczącą dodawania poleceń do modelu ViewModels, Xamarin.Forms definiuje dwie klasy, które implementują ICommandelement : Command i Command<T> gdzie T jest typem argumentów do Execute i CanExecute. Te dwie klasy definiują kilka konstruktorów oraz metodę ChangeCanExecute , którą model ViewModel może wywołać, aby wymusić Command wyzwolenie CanExecuteChanged zdarzenia przez obiekt.

Oto model ViewModel dla prostego klawiatury przeznaczonego do wprowadzania numerów telefonów. Zwróć uwagę, że Execute metoda i CanExecute jest zdefiniowana jako funkcje lambda bezpośrednio w konstruktorze:

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));
        }
    }
}

W tym modelu ViewModel przyjęto założenie, że AddCharCommand właściwość jest powiązana z właściwością Command kilku przycisków (lub dowolnych innych elementów, które mają interfejs polecenia), z których każda jest identyfikowana przez CommandParameterelement . Przyciski te dodają znaki do InputString właściwości, która jest następnie sformatowana jako numer telefonu dla DisplayText właściwości.

Istnieje również druga właściwość typu ICommand o nazwie DeleteCharCommand. Jest to powiązane z przyciskiem odstępu wstecz, ale przycisk powinien być wyłączony, jeśli nie ma znaków do usunięcia.

Poniższy klawiatura nie jest tak wyrafinowana wizualnie, jak to możliwe. Zamiast tego znaczniki zostały zredukowane do minimum, aby zademonstrować bardziej wyraźnie użycie interfejsu poleceń:

<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>

Właściwość Command pierwszego Button , która pojawia się w tym znaczniku, jest powiązana DeleteCharCommandz elementem ; pozostałe są powiązane AddCharCommand z znakiem , CommandParameter który jest taki sam jak znak wyświetlany na Button twarzy. Oto program w działaniu:

Kalkulator przy użyciu maszyny MVVM i poleceń

Wywoływanie metod asynchronicznych

Polecenia mogą również wywoływać metody asynchroniczne. Jest to osiągane przy użyciu async słów kluczowych i await podczas określania Execute metody:

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

Oznacza to, że DownloadAsync metoda jest Task metodą i powinna być oczekiwana:

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

void Download ()
{
    ...
}

Implementowanie menu nawigacji

Przykładowy program zawierający cały kod źródłowy w tej serii artykułów używa modelu ViewModel dla swojej strony głównej. Ten model ViewModel to definicja krótkiej klasy o trzech właściwościach o nazwie Type, Titlei Description zawierających typ każdej z przykładowych stron, tytuł i krótki opis. Ponadto model ViewModel definiuje właściwość statyczną o nazwie All , która jest kolekcją wszystkich stron w programie:

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; }
}

Plik XAML dla definiuje, którego ItemsSource właściwość jest ustawiona na tej All właściwości i która zawiera element TextCell do wyświetlania Title właściwości i Description każdej ListBoxMainPage strony:

<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>

Strony są wyświetlane na liście z możliwością przewijania:

Lista stron z możliwością przewijania

Program obsługi w pliku za kodem jest wyzwalany, gdy użytkownik wybierze element. Procedura obsługi ustawia SelectedItem właściwość ListBox wstecz na null , a następnie tworzy wystąpienie wybranej strony i przechodzi do niej:

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);
    }
}

Wideo

Xamarin Evolve 2016: MVVM Made Simple with Xamarin.Forms and Prism

Podsumowanie

XAML to zaawansowane narzędzie do definiowania interfejsów użytkownika w Xamarin.Forms aplikacjach, szczególnie w przypadku użycia maszyn wirtualnych MVVM i powiązania danych. Wynikiem jest czysta, elegancka i potencjalnie narzędziowa reprezentacja interfejsu użytkownika ze wszystkimi obsługą tła w kodzie.

Więcej filmów na platformie Xamarin można znaleźć w witrynach Channel 9 i YouTube.