Часть 5.Part 5. От привязки данных до MVVMFrom Data Bindings to MVVM

Загрузить образец загрузить примерDownload Sample Download the sample

Шаблон архитектуры Model-View-ViewModel (MVVM) была изобретена с XAML в виду. Шаблон обеспечивает разделение уровней программного обеспечения и три — пользовательский интерфейс XAML, именем представления. базовые данные, называется моделью; а посредником между представлением и моделью, называются ViewModel. View и ViewModel часто соединены через привязки данных, определенные в файле XAML. BindingContext для представления обычно является экземпляром ViewModel.The Model-View-ViewModel (MVVM) architectural pattern was invented with XAML in mind. The pattern enforces a separation between three software layers — the XAML user interface, called the View; the underlying data, called the Model; and an intermediary between the View and the Model, called the ViewModel. The View and the ViewModel are often connected through data bindings defined in the XAML file. The BindingContext for the View is usually an instance of the ViewModel.

Простой модели представленияA Simple ViewModel

Введение в классы ViewModel давайте сначала посмотрим программы без него.As an introduction to ViewModels, let’s first look at a program without one. Ранее вы узнали, как определить новое объявление пространства имен XML, чтобы разрешить файл XAML для ссылочных классов в других сборках.Earlier you saw how to define a new XML namespace declaration to allow a XAML file to reference classes in other assemblies. Вот это программа, которая определяет объявление пространства имен XML для System пространство имен:Here’s a program that defines an XML namespace declaration for the System namespace:

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

Программа может использовать x:Static получить текущую дату и время из статического DateTime.Now свойство и задайте их DateTime значение BindingContext на StackLayout:The program can use x:Static to obtain the current date and time from the static DateTime.Now property and set that DateTime value to the BindingContext on a StackLayout:

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

BindingContext представляет собой очень специальное свойство: При задании BindingContext на элементе, он наследует все дочерние элементы этого элемента.BindingContext is a very special property: When you set the BindingContext on an element, it is inherited by all the children of that element. Это означает, что все дочерние StackLayout этот же BindingContext, и они могут содержать простые привязки к свойствам этого объекта.This means that all the children of the StackLayout have this same BindingContext, and they can contain simple bindings to properties of that object.

В One-Shot DateTime программы, два дочерних содержат привязки к свойствам, DateTime значение, а два других дочерних элементов содержат привязок, которые отсутствуют пути привязки.In the One-Shot DateTime program, two of the children contain bindings to properties of that DateTime value, but two other children contain bindings that seem to be missing a binding path. Это означает, что DateTime само значение используется для StringFormat:This means that the DateTime value itself is used for the StringFormat:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             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>

Конечно большая проблема состоит в дату и время, набор после сначала при построении страницы и никогда не изменяются:Of course, the big problem is that the date and time are set once when the page is first built, and never change:

Файл XAML можно отобразить, всегда показывает текущее время часов, но его необходимо написать код, чтобы оказать помощь. Когда мыслить категориями MVVM, модель и модель представления являются классами, написанных полностью на код.A XAML file can display a clock that always shows the current time, but it needs some code to help out. When thinking in terms of MVVM, the Model and ViewModel are classes written entirely in code. Это представление часто является файл XAML, который ссылается на свойства, определенные в модели представления через привязки данных.The View is often a XAML file that references properties defined in the ViewModel through data bindings.

Правильной модели неведении относительно того, ViewModel и правильный ViewModel неведении представления.A proper Model is ignorant of the ViewModel, and a proper ViewModel is ignorant of the View. Тем не менее очень часто программист применяет типы данных, предоставляемые ViewModel в типы данных, связанный с конкретной пользовательских интерфейсов.However, very often a programmer tailors the data types exposed by the ViewModel to the data types associated with particular user interfaces. Например если модель обращается к базе данных, содержащий строки 8-битовых символов ASCII, ViewModel потребуется преобразование этих строк для строк Юникода в соответствии с их эксклюзивное использование Юникода в пользовательском интерфейсе.For example, if a Model accesses a database that contains 8-bit character ASCII strings, the ViewModel would need to convert between those strings to Unicode strings to accommodate the exclusive use of Unicode in the user interface.

В простые примеры MVVM (например, показано ниже) часто не существует модели и шаблон включает в себя только просматривать и ViewModel соединенные привязки данных.In simple examples of MVVM (such as those shown here), often there is no Model at all, and the pattern involves just a View and ViewModel linked with data bindings.

Вот ViewModel для часы, с использованием только к одному свойству с именем DateTime, но обновлений, которые DateTime свойство каждую секунду:Here’s a ViewModel for a clock with just a single property named DateTime, but which updates that DateTime property every second:

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

Обычно реализуют классы ViewModel INotifyPropertyChanged интерфейс, который означает, что класс запускает PropertyChanged событие при изменении одного из его свойств.ViewModels generally implement the INotifyPropertyChanged interface, which means that the class fires a PropertyChanged event whenever one of its properties changes. Механизм привязки данных в Xamarin.Forms присоединяет обработчик к этому PropertyChanged событий, поэтому он может получать уведомления об изменении свойства и сохранить целевой объект обновляется новое значение.The data binding mechanism in Xamarin.Forms attaches a handler to this PropertyChanged event so it can be notified when a property changes and keep the target updated with the new value.

Часы, в зависимости от этого ViewModel может быть сложнее, чем это:A clock based on this ViewModel can be as simple as this:

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

Обратите внимание, что как ClockViewModel присваивается BindingContext из Label с помощью тегов элементов свойства.Notice how the ClockViewModel is set to the BindingContext of the Label using property element tags. Кроме того, можно создать экземпляр ClockViewModel в Resources коллекции и присвойте ему значение BindingContext через StaticResource расширение разметки.Alternatively, you can instantiate the ClockViewModel in a Resources collection and set it to the BindingContext via a StaticResource markup extension. Кроме того, файл с выделенным кодом можно создавать экземпляры ViewModel.Or, the code-behind file can instantiate the ViewModel.

Binding Расширение разметки на Text свойство Label форматы DateTime свойство.The Binding markup extension on the Text property of the Label formats the DateTime property. Здесь отображается по:Here’s the display:

Также возможен доступ к отдельным свойствам DateTime свойство ViewModel, разделив их свойства с периодами:It’s also possible to access individual properties of the DateTime property of the ViewModel by separating the properties with periods:

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

Интерактивный MVVMInteractive MVVM

MVVM довольно часто используется с помощью двухсторонней привязки для это интерактивное представление на основе базовой модели данных.MVVM is quite often used with two-way data bindings for an interactive view based on an underlying data model.

Вот класс с именем HslViewModel , преобразующий Color значение в Hue, Saturation, и Luminosity значения и наоборот:Here’s a class named HslViewModel that converts a Color value into Hue, Saturation, and Luminosity values, and vice versa:

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

Изменения в Hue, Saturation, и Luminosity причина свойства Color изменение свойства, а также изменения Color вызывает три свойства для изменения.Changes to the Hue, Saturation, and Luminosity properties cause the Color property to change, and changes to Color causes the other three properties to change. Это может показаться бесконечный цикл, за исключением того, что класс не вызвать PropertyChanged событие, если только свойство действительно были изменены.This might seem like an infinite loop, except that the class doesn't invoke the PropertyChanged event unless the property has actually changed. Это означает прекращение цикла в противном случае неконтролируемых обратной связи.This puts an end to the otherwise uncontrollable feedback loop.

Следующий XAML-файл содержит BoxView которого Color свойство привязано к Color свойство ViewModel, а остальные три Slider и три Label представления привязан к Hue, Saturationи Luminosity свойства:The following XAML file contains a BoxView whose Color property is bound to the Color property of the ViewModel, and three Slider and three Label views bound to the Hue, Saturation, and Luminosity properties:

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

Привязка на каждом Label используется по умолчанию OneWay.The binding on each Label is the default OneWay. Его достаточно для отображения значения.It only needs to display the value. Однако привязки на каждом Slider является TwoWay.But the binding on each Slider is TwoWay. Это позволяет Slider инициализированную из ViewModel.This allows the Slider to be initialized from the ViewModel. Обратите внимание, что Color свойству Aqua при создании экземпляра модели представления.Notice that the Color property is set to Aqua when the ViewModel is instantiated. Но изменение Slider также необходимо задать новое значение для свойства в ViewModel, который затем вычисляет новый цвет.But a change in the Slider also needs to set a new value for the property in the ViewModel, which then calculates a new color.

Выполнение команд с помощью модели ViewModelCommanding with ViewModels

Во многих случаях шаблон MVVM ограничен манипуляции элементов данных: Объекты пользовательского интерфейса в представлении параллельные объекты данных в модели представления.In many cases, the MVVM pattern is restricted to the manipulation of data items: User-interface objects in the View parallel data objects in the ViewModel.

Тем не менее иногда представления должен содержать кнопки, вызова различных действий в ViewModel.However, sometimes the View needs to contain buttons that trigger various actions in the ViewModel. Но не может содержать ViewModel Clicked обработчики для кнопок, так как, будет привязана ViewModel парадигме конкретного пользовательского интерфейса.But the ViewModel must not contain Clicked handlers for the buttons because that would tie the ViewModel to a particular user-interface paradigm.

Чтобы разрешить ViewModel, чтобы быть более независимой от объектов определенного пользовательского интерфейса, но по-прежнему допускает методов, вызываемых в ViewModel, команда интерфейс существует.To allow ViewModels to be more independent of particular user interface objects but still allow methods to be called within the ViewModel, a command interface exists. Этот интерфейс командной поддерживает следующие элементы в Xamarin.Forms.This command interface is supported by the following elements in Xamarin.Forms:

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (и, следовательно также ImageCell)TextCell (and hence also ImageCell)
  • ListView
  • TapGestureRecognizer

За исключением элемента SearchBar и ListView элемента, эти элементы определяют два свойства:With the exception of the SearchBar and ListView element, these elements define two properties:

  • Command типа System.Windows.Input.ICommandCommand of type System.Windows.Input.ICommand
  • CommandParameter типа ObjectCommandParameter of type Object

SearchBar Определяет SearchCommand и SearchCommandParameter свойства, хотя ListView определяет RefreshCommand свойство типа ICommand.The SearchBar defines SearchCommand and SearchCommandParameter properties, while the ListView defines a RefreshCommand property of type ICommand.

ICommand Интерфейс определяет два метода и одно событие:The ICommand interface defines two methods and one event:

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

ViewModel можно определить свойства типа ICommand.The ViewModel can define properties of type ICommand. Теперь можно привязать эти свойства, чтобы Command свойства каждого Button или другой элемент, или возможно пользовательские представления, которое реализует этот интерфейс.You can then bind these properties to the Command property of each Button or other element, or perhaps a custom view that implements this interface. При необходимости можно задать CommandParameter свойство для идентификации отдельных Button объектов (или другие элементы), привязанные к этому свойству модели представления.You can optionally set the CommandParameter property to identify individual Button objects (or other elements) that are bound to this ViewModel property. На внутреннем уровне Button вызовы Execute метод всякий раз, когда пользователь касается Button, передав для Execute метод его CommandParameter.Internally, the Button calls the Execute method whenever the user taps the Button, passing to the Execute method its CommandParameter.

CanExecute Метод и CanExecuteChanged событий используются для случаев, где Button tap может быть недопустимых в данный момент, в этом случае Button следует отключить сам.The CanExecute method and CanExecuteChanged event are used for cases where a Button tap might be currently invalid, in which case the Button should disable itself. Button Вызовы CanExecute при Command сначала задано и в любое время CanExecuteChanged события.The Button calls CanExecute when the Command property is first set and whenever the CanExecuteChanged event is fired. Если CanExecute возвращает false, Button отключается и не генерирует Execute вызовов.If CanExecute returns false, the Button disables itself and doesn’t generate Execute calls.

Для справки при добавлении команды для своих ViewModel Xamarin.Forms определяет два класса, которые реализуют ICommand: Command и Command<T> где T — это тип аргументов Execute и CanExecute.For help in adding commanding to your ViewModels, Xamarin.Forms defines two classes that implement ICommand: Command and Command<T> where T is the type of the arguments to Execute and CanExecute. Эти классы определяют несколько конструкторов, а также ChangeCanExecute метод, который можно вызвать ViewModel, чтобы принудительно Command объект, подлежащий CanExecuteChanged событий.These two classes define several constructors plus a ChangeCanExecute method that the ViewModel can call to force the Command object to fire the CanExecuteChanged event.

Вот ViewModel для простой клавиатуре, который предназначен для ввода номера телефона.Here is a ViewModel for a simple keypad that is intended for entering telephone numbers. Обратите внимание, что Execute и CanExecute метод определяются как лямбда-выражение прямо в конструкторе:Notice that the Execute and CanExecute method are defined as lambda functions right in the constructor:

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

Предполагается, что этого ViewModel AddCharCommand свойство привязано к Command свойство несколько кнопок (или все, что содержит интерфейс команды), каждый из которых идентифицируется по CommandParameter.This ViewModel assumes that the AddCharCommand property is bound to the Command property of several buttons (or anything else that has a command interface), each of which is identified by the CommandParameter. Эти кнопки позволяют добавлять символы для InputString свойство, которое затем форматируется как номер телефона для DisplayText свойство.These buttons add characters to an InputString property, which is then formatted as a phone number for the DisplayText property.

Имеется также свойство типа ICommand с именем DeleteCharCommand.There is also a second property of type ICommand named DeleteCharCommand. Привязывается к кнопке назад пробелов, но кнопки следует отключить, если нет символов для удаления.This is bound to a back-spacing button, but the button should be disabled if there are no characters to delete.

Следующие клавиатуре не является сложных как визуально, как хотелось бы.The following keypad is not as visually sophisticated as it could be. Вместо этого разметка разбивается на как минимум для демонстрации более четко использование интерфейса командной:Instead, the markup has been reduced to a minimum to demonstrate more clearly the use of the command interface:

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

Command Свойства первого Button , отображаемый в этом разметки привязан к DeleteCharCommand; остальные привязаны к AddCharCommand с CommandParameter то есть так же, как символ, который отображается на Button лиц.The Command property of the first Button that appears in this markup is bound to the DeleteCharCommand; the rest are bound to the AddCharCommand with a CommandParameter that is the same as the character that appears on the Button face. Вот программа в действии:Here’s the program in action:

Вызов асинхронных методовInvoking Asynchronous Methods

Команды также можно вызывать асинхронные методы.Commands can also invoke asynchronous methods. Это достигается с помощью async и await ключевые слова, при указании Execute метод:This is achieved by using the async and await keywords when specifying the Execute method:

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

Это означает, что DownloadAsync метод Task и следует ожидать:This indicates that the DownloadAsync method is a Task and should be awaited:

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

void Download ()
{
    ...
}

Реализация меню навигацииImplementing a Navigation Menu

XamlSamples программа, которая содержит весь исходный код в этой серии статей использует модель представления для его домашней страницы.The XamlSamples program that contains all the source code in this series of articles uses a ViewModel for its home page. Этого ViewModel — это определение класса с коротким три свойства с именем Type, Title, и Description , содержащие тип каждого из образцов страниц, название и краткое описание.This ViewModel is a definition of a short class with three properties named Type, Title, and Description that contain the type of each of the sample pages, a title, and a short description. Кроме того, ViewModel определяет статическое свойство с именем All , являющийся коллекцией всех страниц в программе:In addition, the ViewModel defines a static property named All that is a collection of all the pages in the program:

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

Файл XAML для MainPage определяет ListBox которого ItemsSource свойству, All свойство и который содержит TextCell для отображения Title и Description свойства каждой страницы:The XAML file for MainPage defines a ListBox whose ItemsSource property is set to that All property and which contains a TextCell for displaying the Title and Description properties of each page:

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

Страницы отображаются в прокручиваемый список:The pages are shown in a scrollable list:

Обработчик в файле кода активируется в том случае, когда пользователь выбирает элемент.The handler in the code-behind file is triggered when the user selects an item. Задает обработчик SelectedItem свойство ListBox обратно null и затем создает экземпляр выбранной страницы и переходу на нее:The handler sets the SelectedItem property of the ListBox back to null and then instantiates the selected page and navigates to it:

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, ставшая простой благодаря Xamarin.Forms и PrismXamarin Evolve 2016: MVVM Made Simple with Xamarin.Forms and Prism

СводкаSummary

XAML — это мощное средство для определения пользовательских интерфейсов приложений Xamarin.Forms, особенно если привязки данных и используются MVVM.XAML is a powerful tool for defining user interfaces in Xamarin.Forms applications, particularly when data-binding and MVVM are used. Результат — это чистая, элегантные и потенциально оснащен инструментами, представление пользовательского интерфейса с поддержкой всех фона в коде.The result is a clean, elegant, and potentially toolable representation of a user interface with all the background support in code.