Режим привязки Xamarin.FormsXamarin.Forms Binding Mode

Скачать пример Скачать примерDownload Sample Download the sample

В предыдущей статье, в разделах Альтернативная привязка кода и Альтернативная привязка XAML, объект Label со свойством Scale привязывался к свойству Value объекта Slider.In the previous article, the Alternative Code Binding and Alternative XAML Binding pages featured a Label with its Scale property bound to the Value property of a Slider. Так как начальное значение Slider равно 0, свойство Scale объекта Label также было равно 0, а не 1, поэтому объект Label исчезал.Because the Slider initial value is 0, this caused the Scale property of the Label to be set to 0 rather than 1, and the Label disappeared.

В примере DataBindingDemos страница Обратная привязка аналогична программам в предыдущей статье, только привязка данных определяется для Slider, а не Label:In the DataBindingDemos sample, the Reverse Binding page is similar to the programs in the previous article, except that the data binding is defined on the Slider rather than on the Label:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.ReverseBindingPage"
             Title="Reverse Binding">
    <StackLayout Padding="10, 0">

        <Label x:Name="label"
               Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                VerticalOptions="CenterAndExpand"
                Value="{Binding Source={x:Reference label},
                                Path=Opacity}" />
    </StackLayout>
</ContentPage>

Поначалу может показаться, что все наоборот: теперь Label является источником привязки данных, а Slider — целевым объектом.At first, this might seem backwards: Now the Label is the data-binding source, and the Slider is the target. Привязка ссылается на свойство Opacity объекта Label, которое имеет значение по умолчанию 1.The binding references the Opacity property of the Label, which has a default value of 1.

Как можно догадаться, Slider инициализируется со значением 1 из начального значения Opacity объекта Label.As you might expect, the Slider is initialized to the value 1 from the initial Opacity value of Label. Это показано на снимке экрана iOS слева.This is shown in the iOS screenshot on the left:

Обратная привязкаReverse Binding

Вы удивитесь, но объект Slider продолжает работать, как показано на снимках экрана Android и UWP.But you might be surprised that the Slider continues to work, as the Android and UWP screenshots demonstrate. Кажется, что привязка данных работает лучше, когда Slider является целевым объектом привязки вместо Label, так как инициализация работает, как и ожидалось.This seems to suggest that the data binding works better when the Slider is the binding target rather than the Label because the initialization works like we might expect.

Разница между примером обратной привязки и более ранними примерами заключается в режиме привязки.The difference between the Reverse Binding sample and the earlier samples involves the binding mode.

Режим привязки по умолчаниюThe Default Binding Mode

Режим привязки указывается с помощью члена перечисления BindingMode:The binding mode is specified with a member of the BindingMode enumeration:

  • Default
  • TwoWay – данные передаются в обе стороны между целевым объектом и источником.TwoWay – data goes both ways between source and target
  • OneWay – данные передаются от источника к целевому объекту.OneWay – data goes from source to target
  • OneWayToSource – данные передаются от целевого объекта к источнику.OneWayToSource – data goes from target to source
  • OneTime – данные передаются от источника к целевому объекту, но только если BindingContext меняется (новая возможность в Xamarin.Forms 3.0).OneTime – data goes from source to target, but only when the BindingContext changes (new with Xamarin.Forms 3.0)

Каждое привязываемое свойство имеет режим привязки по умолчанию, который задается при создании привязываемого свойства и доступен из свойства DefaultBindingMode объекта BindableProperty.Every bindable property has a default binding mode that is set when the bindable property is created, and which is available from the DefaultBindingMode property of the BindableProperty object. Этот режим привязки по умолчанию указывает режим, действующий, когда свойство является целевым объектом привязки данных.This default binding mode indicates the mode in effect when that property is a data-binding target.

Режим привязки по умолчанию для большинства свойств, таких как Rotation, Scale и Opacity, — OneWay.The default binding mode for most properties such as Rotation, Scale, and Opacity is OneWay. Если эти свойства являются целевыми объектами привязки данных, целевое свойство устанавливается из источника.When these properties are data-binding targets, then the target property is set from the source.

Но режим привязки по умолчанию для свойства Value объекта Slider — TwoWay.However, the default binding mode for the Value property of Slider is TwoWay. Это означает, что, когда свойство Value является целевым объектом привязки данных, целевой объект задается из источника (как обычно), но источник также задается из целевого объекта.This means that when the Value property is a data-binding target, then the target is set from the source (as usual) but the source is also set from the target. Это позволяет задать Slider из первоначального значения Opacity.This is what allows the Slider to be set from the initial Opacity value.

Может показаться, что такая двусторонняя привязка создаст бесконечный цикл, но этого не происходит.This two-way binding might seem to create an infinite loop, but that doesn't happen. Привязываемые свойства не сигнализируют об изменении свойства, пока свойство не меняется.Bindable properties do not signal a property change unless the property actually changes. Тем самым предотвращается бесконечный цикл.This prevents an infinite loop.

Двусторонние привязкиTwo-Way Bindings

У большинства привязываемых свойств режим привязки по умолчанию — OneWay, но следующие свойства имеют режим привязки по умолчанию TwoWay:Most bindable properties have a default binding mode of OneWay but the following properties have a default binding mode of TwoWay:

  • свойство Date объекта DatePicker;Date property of DatePicker
  • свойство Text объектов Editor, Entry, SearchBar, и EntryCell;Text property of Editor, Entry, SearchBar, and EntryCell
  • свойство IsRefreshing объекта ListView;IsRefreshing property of ListView
  • свойство SelectedItem объекта MultiPage;SelectedItem property of MultiPage
  • свойства SelectedIndex и SelectedItem объектов Picker;SelectedIndex and SelectedItem properties of Picker
  • свойство Value объектов Slider и Stepper;Value property of Slider and Stepper
  • свойство IsToggled объекта Switch;IsToggled property of Switch
  • свойство On объекта SwitchCell;On property of SwitchCell
  • свойство Time объекта TimePicker.Time property of TimePicker

Эти конкретные свойства определяются как TwoWay не просто так.These particular properties are defined as TwoWay for a very good reason:

При использовании привязки данных с архитектурой приложения "модель — представление — модель представления" (MVVM) класс ViewModel является источником привязки данных, а класс View, который состоит из таких представлений, как Slider, является целевым объектом привязки.When data bindings are used with the Model-View-ViewModel (MVVM) application architecture, the ViewModel class is the data-binding source, and the View, which consists of views such as Slider, are data-binding targets. Привязки MVVM напоминают пример обратной привязки больше, чем привязки в предыдущих примерах.MVVM bindings resemble the Reverse Binding sample more than the bindings in the previous samples. Скорее всего, вы хотите, чтобы каждое представление на странице инициализировалось со значением соответствующего свойства в модели представления, но изменения в представлении также влияли на свойство модели представления.It is very likely that you want each view on the page to be initialized with the value of the corresponding property in the ViewModel, but changes in the view should also affect the ViewModel property.

Свойства с режимом привязки по умолчанию TwoWay чаще всего используются в сценариях MVVM.The properties with default binding modes of TwoWay are those properties most likely to be used in MVVM scenarios.

Односторонние привязки к источникуOne-Way-to-Source Bindings

Привязываемые свойства только для чтения используют режим привязки по умолчанию OneWayToSource.Read-only bindable properties have a default binding mode of OneWayToSource. Есть только одно привязываемое свойство для чтения и записи с режимом привязки по умолчанию OneWayToSource:There is only one read/write bindable property that has a default binding mode of OneWayToSource:

  • свойство SelectedItem объекта ListView.SelectedItem property of ListView

Дело в том, что привязка к свойству SelectedItem должна приводить к заданию источника привязки.The rationale is that a binding on the SelectedItem property should result in setting the binding source. Пример далее в этой статье переопределяет это поведение.An example later in this article overrides that behavior.

Одноразовые привязкиOne-Time Bindings

Несколько свойств используют режим привязки по умолчанию OneTime, включая свойство IsTextPredictionEnabled для Entry.Several properties have a default binding mode of OneTime, including the IsTextPredictionEnabled property of Entry.

Целевые свойства с режимом привязки OneTime обновляются только при изменении контекста привязки.Target properties with a binding mode of OneTime are updated only when the binding context changes. Для привязок к этим целевым свойствам это упрощает инфраструктуру привязки, так как не нужно отслеживать изменения в свойствах источника.For bindings on these target properties, this simplifies the binding infrastructure because it is not necessary to monitor changes in the source properties.

Уведомления об изменении свойства и моделей представленияViewModels and Property-Change Notifications

Страница Простой селектор цвета демонстрирует использование простой модели представления.The Simple Color Selector page demonstrates the use of a simple ViewModel. Привязки данных позволяют пользователю выбрать цвет с использованием трех элементов Slider для оттенка, насыщенности и яркости.Data bindings allow the user to select a color using three Slider elements for the hue, saturation, and luminosity.

Модель представления является источником привязки данных.The ViewModel is the data-binding source. Модель представления не определяет привязываемые свойства, но реализует механизм уведомлений, который позволяет инфраструктуре узнавать об изменении значения свойства.The ViewModel does not define bindable properties, but it does implement a notification mechanism that allows the binding infrastructure to be notified when the value of a property changes. Этим механизмом уведомлений является интерфейс INotifyPropertyChanged, который определяет одно событие с именем PropertyChanged.This notification mechanism is the INotifyPropertyChanged interface, which defines a single event named PropertyChanged. Класс, реализующий этот интерфейс, обычно запускает событие, когда одно из его открытых свойств меняет значение.A class that implements this interface generally fires the event when one of its public properties changes value. Событие не инициируется, если свойство никогда не меняется.The event does not need to be fired if the property never changes. (Интерфейс INotifyPropertyChanged также реализуется объектом BindableObject, и событие PropertyChanged возникает при изменении значения привязываемого свойства.)(The INotifyPropertyChanged interface is also implemented by BindableObject and a PropertyChanged event is fired whenever a bindable property changes value.)

Класс HslColorViewModel определяет пять свойств: свойства Hue, Saturation, Luminosity и Color взаимосвязаны.The HslColorViewModel class defines five properties: The Hue, Saturation, Luminosity, and Color properties are interrelated. Если один из трех компонентов цвета меняет значение, свойство Color пересчитывается и события PropertyChanged запускаются для всех четырех свойств:When any one of the three color components changes value, the Color property is recalculated, and PropertyChanged events are fired for all four properties:

public class HslColorViewModel : INotifyPropertyChanged
{
    Color color;
    string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public double Hue
    {
        set
        {
            if (color.Hue != value)
            {
                Color = Color.FromHsla(value, color.Saturation, color.Luminosity);
            }
        }
        get
        {
            return color.Hue;
        }
    }

    public double Saturation
    {
        set
        {
            if (color.Saturation != value)
            {
                Color = Color.FromHsla(color.Hue, value, color.Luminosity);
            }
        }
        get
        {
            return color.Saturation;
        }
    }

    public double Luminosity
    {
        set
        {
            if (color.Luminosity != value)
            {
                Color = Color.FromHsla(color.Hue, color.Saturation, value);
            }
        }
        get
        {
            return color.Luminosity;
        }
    }

    public Color Color
    {
        set
        {
            if (color != value)
            {
                color = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

                Name = NamedColor.GetNearestColorName(color);
            }
        }
        get
        {
            return color;
        }
    }

    public string Name
    {
        private set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
        get
        {
            return name;
        }
    }
}

Когда свойство Color меняется, статический метод GetNearestColorName в классе NamedColor (также входит в решение DataBindingDemos) получает ближайший именованный цвет и задает свойство Name.When the Color property changes, the static GetNearestColorName method in the NamedColor class (also included in the DataBindingDemos solution) obtains the closest named color and sets the Name property. Это свойство Name имеет закрытый метод доступа set, поэтому его нельзя установить за пределами класса.This Name property has a private set accessor, so it cannot be set from outside the class.

Если модель представления задана как источник привязки, инфраструктура привязки присоединяет обработчик к событию PropertyChanged.When a ViewModel is set as a binding source, the binding infrastructure attaches a handler to the PropertyChanged event. Таким образом, привязка получает уведомления об изменениях свойств и может задать целевые свойства на основе измененных значений.In this way, the binding can be notified of changes to the properties, and can then set the target properties from the changed values.

Тем не менее, если целевое свойство (или определение Binding для целевого свойства) имеет режим привязки BindingMode OneTime, инфраструктура привязки может не привязывать обработчик для события PropertyChanged.However, when a target property (or the Binding definition on a target property) has a BindingMode of OneTime, it is not necessary for the binding infrastructure to attach a handler on the PropertyChanged event. Целевое свойство обновляется только тогда, когда BindingContext меняется, а не когда меняется само свойство источника.The target property is updated only when the BindingContext changes and not when the source property itself changes.

Файл XAML Простой селектор цвета создает экземпляр HslColorViewModel в словаре ресурсов страницы и инициализирует свойство Color.The Simple Color Selector XAML file instantiates the HslColorViewModel in the page's resource dictionary and initializes the Color property. Свойство BindingContext объекта Grid устанавливается в расширение привязки StaticResource для ссылки на этот ресурс:The BindingContext property of the Grid is set to a StaticResource binding extension to reference that resource:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SimpleColorSelectorPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:HslColorViewModel x:Key="viewModel"
                                     Color="MediumTurquoise" />

            <Style TargetType="Slider">
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid BindingContext="{StaticResource viewModel}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <BoxView Color="{Binding Color}"
                 Grid.Row="0" />

        <StackLayout Grid.Row="1"
                     Margin="10, 0">

            <Label Text="{Binding Name}"
                   HorizontalTextAlignment="Center" />

            <Slider Value="{Binding Hue}" />

            <Slider Value="{Binding Saturation}" />

            <Slider Value="{Binding Luminosity}" />
        </StackLayout>
    </Grid>
</ContentPage>

BoxView, Label и три представления Slider наследуют контекст привязки из Grid.The BoxView, Label, and three Slider views inherit the binding context from the Grid. Эти представления являются адресатами (целевыми объектами) привязки, которые ссылаются на свойства источника в модели представления.These views are all binding targets that reference source properties in the ViewModel. Для свойства Color объекта BoxView и свойства Text объекта Label привязки данных имеют режим OneWay: свойства в представлении задаются по свойствам в модели представления.For the Color property of the BoxView, and the Text property of the Label, the data bindings are OneWay: The properties in the view are set from the properties in the ViewModel.

Но свойство Value объекта Slider имеет режим TwoWay.The Value property of the Slider, however, is TwoWay. Поэтому каждый объект Slider задается из модели представления, а модель представления задается из каждого объекта Slider.This allows each Slider to be set from the ViewModel, and also for the ViewModel to be set from each Slider.

При первом запуске программы BoxView, Label и три элемента Slider получают значения из модели представления в зависимости от первоначального свойства Color, заданного при создании экземпляра модели представления.When the program is first run, the BoxView, Label, and three Slider elements are all set from the ViewModel based on the initial Color property set when the ViewModel was instantiated. Это показано на снимке экрана iOS слева.This is shown in the iOS screenshot at the left:

Простой селектор цветаSimple Color Selector

Когда вы перемещаете ползунки, объекты BoxView и Label обновляются соответствующим образом, как показано на снимках экрана Android и UWP.As you manipulate the sliders, the BoxView and Label are updated accordingly, as illustrated by the Android and UWP screenshots.

Создание экземпляров модели представления в словаре ресурсов является распространенным подходом.Instantiating the ViewModel in the resource dictionary is one common approach. Можно также создать экземпляр модели представления в тегах элемента свойства для свойства BindingContext.It's also possible to instantiate the ViewModel within property element tags for the BindingContext property. В файле XAML Простой селектор цвета попробуйте удалить HslColorViewModel из словаря ресурсов и задать его свойством BindingContext объекта Grid следующим образом.In the Simple Color Selector XAML file, try removing the HslColorViewModel from the resource dictionary and set it to the BindingContext property of the Grid like this:

<Grid>
    <Grid.BindingContext>
        <local:HslColorViewModel Color="MediumTurquoise" />
    </Grid.BindingContext>

    ···

</Grid>

Контекст привязки можно задать различными способами.The binding context can be set in a variety of ways. В некоторых случаях файл с выделенным кодом создает экземпляр модели представления и задает его свойством BindingContext страницы.Sometimes, the code-behind file instantiates the ViewModel and sets it to the BindingContext property of the page. Это все допустимые подходы.These are all valid approaches.

Переопределение режима привязкиOverriding the Binding Mode

Если режим привязки целевого свойства по умолчанию не подходит для конкретной привязки данных, можно переопределить его, задав свойство Mode объекта Binding (или свойство Mode расширения разметки Binding) одному из членов перечисления BindingMode.If the default binding mode on the target property is not suitable for a particular data binding, it's possible to override it by setting the Mode property of Binding (or the Mode property of the Binding markup extension) to one of the members of the BindingMode enumeration.

Однако, если указать для свойства Mode значение TwoWay, оно не всегда будет работать так, как вы ожидаете.However, setting the Mode property to TwoWay doesn't always work as you might expect. Например, попробуйте изменить файл XAML Альтернативная привязка XAML, чтобы включить TwoWay в определение привязки:For example, try modifying the Alternative XAML Binding XAML file to include TwoWay in the binding definition:

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       Scale="{Binding Source={x:Reference slider},
                       Path=Value,
                       Mode=TwoWay}" />

Можно ожидать, что объект Slider будет инициализироваться с начальным значением свойства Scale, равным 1, но этого не происходит.It might be expected that the Slider would be initialized to the initial value of the Scale property, which is 1, but that doesn't happen. Когда привязка TwoWay инициализируется, целевой объект сначала получает значение из источника, то есть свойство Scale имеет значение объекта Slider по умолчанию, равное 0.When a TwoWay binding is initialized, the target is set from the source first, which means that the Scale property is set to the Slider default value of 0. Когда привязка TwoWay устанавливается для объекта Slider, объект Slider получает первоначальное значение из источника.When the TwoWay binding is set on the Slider, then the Slider is initially set from the source.

Можно задать режим привязки OneWayToSource в примере альтернативной привязки XAML:You can set the binding mode to OneWayToSource in the Alternative XAML Binding sample:

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       Scale="{Binding Source={x:Reference slider},
                       Path=Value,
                       Mode=OneWayToSource}" />

Теперь Slider инициализируется со значением 1 (значение по умолчанию свойства Scale), но изменение Slider не влияет на свойство Scale, так что это не очень полезно.Now the Slider is initialized to 1 (the default value of Scale) but manipulating the Slider doesn't affect the Scale property, so this is not very useful.

Примечание

Класс VisualElement также определяет свойства ScaleX и ScaleY, которые могут масштабировать VisualElement по-разному в горизонтальном и вертикальном направлениях.The VisualElement class also defines ScaleX and ScaleY properties, which can scale the VisualElement differently in the horizontal and vertical directions.

Удобное переопределение режима привязки по умолчанию на TwoWay включает в себя свойство SelectedItem объекта ListView.A very useful application of overriding the default binding mode with TwoWay involves the SelectedItem property of ListView. Режим привязки по умолчанию — OneWayToSource.The default binding mode is OneWayToSource. Если привязка данных задана для свойства SelectedItem и ссылается на свойство источника в модели представления, то это свойство источника задается из ListView.When a data binding is set on the SelectedItem property to reference a source property in a ViewModel, then that source property is set from the ListView selection. Но в некоторых случаях можно также инициализировать ListView из модели представления.However, in some circumstances, you might also want the ListView to be initialized from the ViewModel.

Страница Примеры параметров демонстрирует этот подход.The Sample Settings page demonstrates this technique. Эта страница представляет простую реализацию параметров приложения, которые часто определены в модели представления, например файл SampleSettingsViewModel:This page represents a simple implementation of application settings, which are very often defined in a ViewModel, such as this SampleSettingsViewModel file:

public class SampleSettingsViewModel : INotifyPropertyChanged
{
    string name;
    DateTime birthDate;
    bool codesInCSharp;
    double numberOfCopies;
    NamedColor backgroundNamedColor;

    public event PropertyChangedEventHandler PropertyChanged;

    public SampleSettingsViewModel(IDictionary<string, object> dictionary)
    {
        Name = GetDictionaryEntry<string>(dictionary, "Name");
        BirthDate = GetDictionaryEntry(dictionary, "BirthDate", new DateTime(1980, 1, 1));
        CodesInCSharp = GetDictionaryEntry<bool>(dictionary, "CodesInCSharp");
        NumberOfCopies = GetDictionaryEntry(dictionary, "NumberOfCopies", 1.0);
        BackgroundNamedColor = NamedColor.Find(GetDictionaryEntry(dictionary, "BackgroundNamedColor", "White"));
    }

    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }

    public DateTime BirthDate
    {
        set { SetProperty(ref birthDate, value); }
        get { return birthDate; }
    }

    public bool CodesInCSharp
    {
        set { SetProperty(ref codesInCSharp, value); }
        get { return codesInCSharp; }
    }

    public double NumberOfCopies
    {
        set { SetProperty(ref numberOfCopies, value); }
        get { return numberOfCopies; }
    }

    public NamedColor BackgroundNamedColor
    {
        set
        {
            if (SetProperty(ref backgroundNamedColor, value))
            {
                OnPropertyChanged("BackgroundColor");
            }
        }
        get { return backgroundNamedColor; }
    }

    public Color BackgroundColor
    {
        get { return BackgroundNamedColor?.Color ?? Color.White; }
    }

    public void SaveState(IDictionary<string, object> dictionary)
    {
        dictionary["Name"] = Name;
        dictionary["BirthDate"] = BirthDate;
        dictionary["CodesInCSharp"] = CodesInCSharp;
        dictionary["NumberOfCopies"] = NumberOfCopies;
        dictionary["BackgroundNamedColor"] = BackgroundNamedColor.Name;
    }

    T GetDictionaryEntry<T>(IDictionary<string, object> dictionary, string key, T defaultValue = default(T))
    {
        return dictionary.ContainsKey(key) ? (T)dictionary[key] : defaultValue;
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Каждый параметр приложения — это свойство, которое сохраняется в словаре свойств Xamarin.Forms в методе с именем SaveState и загружается из этого словаря в конструкторе.Each application setting is a property that is saved to the Xamarin.Forms properties dictionary in a method named SaveState and loaded from that dictionary in the constructor. В конце класса есть два метода, которые помогают упростить модели представлений и снизить вероятность ошибок в них.Towards the bottom of the class are two methods that help streamline ViewModels and make them less prone to errors. Метод OnPropertyChanged внизу имеет необязательный параметр, который задается как свойство вызова.The OnPropertyChanged method at the bottom has an optional parameter that is set to the calling property. Это позволяет избежать орфографических ошибок при указании имени свойства в виде строки.This avoids spelling errors when specifying the name of the property as a string.

Метод SetProperty в классе выполняет следующее. Он сравнивает значение, которое задается для свойства, со значением, хранящимся в виде поля, и вызывает OnPropertyChanged, только когда эти два значения не равны.The SetProperty method in the class does even more: It compares the value that is being set to the property with the value stored as a field, and only calls OnPropertyChanged when the two values are not equal.

Класс SampleSettingsViewModel определяет два свойства для цвета фона: свойство BackgroundNamedColor имеет тип NamedColor, который как класс также входит в решение DataBindingDemos.The SampleSettingsViewModel class defines two properties for the background color: The BackgroundNamedColor property is of type NamedColor, which is a class also included in the DataBindingDemos solution. Свойство BackgroundColor имеет тип Color и получается из свойства Color объекта NamedColor.The BackgroundColor property is of type Color, and is obtained from the Color property of the NamedColor object.

Класс NamedColor использует отражение .NET для перечисления всех открытых статических полей в структуре Color Xamarin.Forms и их хранения с именами в коллекции, доступной из статического свойства All:The NamedColor class uses .NET reflection to enumerate all the static public fields in the Xamarin.Forms Color structure, and to store them with their names in a collection accessible from the static All property:

public class NamedColor : IEquatable<NamedColor>, IComparable<NamedColor>
{
    // Instance members
    private NamedColor()
    {
    }

    public string Name { private set; get; }

    public string FriendlyName { private set; get; }

    public Color Color { private set; get; }

    public string RgbDisplay { private set; get; }

    public bool Equals(NamedColor other)
    {
        return Name.Equals(other.Name);
    }

    public int CompareTo(NamedColor other)
    {
        return Name.CompareTo(other.Name);
    }

    // Static members
    static NamedColor()
    {
        List<NamedColor> all = new List<NamedColor>();
        StringBuilder stringBuilder = new StringBuilder();

        // Loop through the public static fields of the Color structure.
        foreach (FieldInfo fieldInfo in typeof(Color).GetRuntimeFields())
        {
            if (fieldInfo.IsPublic &&
                fieldInfo.IsStatic &&
                fieldInfo.FieldType == typeof(Color))
            {
                // Convert the name to a friendly name.
                string name = fieldInfo.Name;
                stringBuilder.Clear();
                int index = 0;

                foreach (char ch in name)
                {
                    if (index != 0 && Char.IsUpper(ch))
                    {
                        stringBuilder.Append(' ');
                    }
                    stringBuilder.Append(ch);
                    index++;
                }

                // Instantiate a NamedColor object.
                Color color = (Color)fieldInfo.GetValue(null);

                NamedColor namedColor = new NamedColor
                {
                    Name = name,
                    FriendlyName = stringBuilder.ToString(),
                    Color = color,
                    RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
                                                (int)(255 * color.R),
                                                (int)(255 * color.G),
                                                (int)(255 * color.B))
                };

                // Add it to the collection.
                all.Add(namedColor);
            }
        }
        all.TrimExcess();
        all.Sort();
        All = all;
    }

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

    public static NamedColor Find(string name)
    {
        return ((List<NamedColor>)All).Find(nc => nc.Name == name);
    }

    public static string GetNearestColorName(Color color)
    {
        double shortestDistance = 1000;
        NamedColor closestColor = null;

        foreach (NamedColor namedColor in NamedColor.All)
        {
            double distance = Math.Sqrt(Math.Pow(color.R - namedColor.Color.R, 2) +
                                        Math.Pow(color.G - namedColor.Color.G, 2) +
                                        Math.Pow(color.B - namedColor.Color.B, 2));

            if (distance < shortestDistance)
            {
                shortestDistance = distance;
                closestColor = namedColor;
            }
        }
        return closestColor.Name;
    }
}

Класс App в проекте DataBindingDemos определяет свойство с именем Settings типа SampleSettingsViewModel.The App class in the DataBindingDemos project defines a property named Settings of type SampleSettingsViewModel. Это свойство инициализируется, когда создается экземпляр класса App, а метод SaveState вызывается, когда вызывается метод OnSleep:This property is initialized when the App class is instantiated, and the SaveState method is called when the OnSleep method is called:

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        Settings = new SampleSettingsViewModel(Current.Properties);

        MainPage = new NavigationPage(new MainPage());
    }

    public SampleSettingsViewModel Settings { private set; get; }

    protected override void OnStart()
    {
        // Handle when your app starts
    }

    protected override void OnSleep()
    {
        // Handle when your app sleeps
        Settings.SaveState(Current.Properties);
    }

    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

Дополнительные сведения о методах жизненного цикла приложения см. в статье Жизненный цикл приложения.For more information on the application lifecycle methods, see the article App Lifecycle.

Практически все остальное обрабатывается в файле SampleSettingsPage.xaml.Almost everything else is handled in the SampleSettingsPage.xaml file. Параметр страницы BindingContext задается с помощью расширения разметки Binding: источник привязки является статическим свойством Application.Current, которое является экземпляром класса App в проекте, а Path задается по свойству Settings, которое является объектом SampleSettingsViewModel.The BindingContext of the page is set using a Binding markup extension: The binding source is the static Application.Current property, which is the instance of the App class in the project, and the Path is set to the Settings property, which is the SampleSettingsViewModel object:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SampleSettingsPage"
             Title="Sample Settings"
             BindingContext="{Binding Source={x:Static Application.Current},
                                      Path=Settings}">

    <StackLayout BackgroundColor="{Binding BackgroundColor}"
                 Padding="10"
                 Spacing="10">

        <StackLayout Orientation="Horizontal">
            <Label Text="Name: "
                   VerticalOptions="Center" />

            <Entry Text="{Binding Name}"
                   Placeholder="your name"
                   HorizontalOptions="FillAndExpand"
                   VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Birth Date: "
                   VerticalOptions="Center" />

            <DatePicker Date="{Binding BirthDate}"
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Do you code in C#? "
                   VerticalOptions="Center" />

            <Switch IsToggled="{Binding CodesInCSharp}"
                    VerticalOptions="Center" />
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="Number of Copies: "
                   VerticalOptions="Center" />

            <Stepper Value="{Binding NumberOfCopies}"
                     VerticalOptions="Center" />

            <Label Text="{Binding NumberOfCopies}"
                   VerticalOptions="Center" />
        </StackLayout>

        <Label Text="Background Color:" />

        <ListView x:Name="colorListView"
                  ItemsSource="{x:Static local:NamedColor.All}"
                  SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"
                  VerticalOptions="FillAndExpand"
                  RowHeight="40">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <BoxView Color="{Binding Color}"
                                     HeightRequest="32"
                                     WidthRequest="32"
                                     VerticalOptions="Center" />

                            <Label Text="{Binding FriendlyName}"
                                   FontSize="24"
                                   VerticalOptions="Center" />
                        </StackLayout>                        
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

Все дочерние элементы страницы наследуют контекст привязки.All the children of the page inherit the binding context. Большинство других привязок на этой странице устанавливаются для свойств в SampleSettingsViewModel.Most of the other bindings on this page are to properties in SampleSettingsViewModel. Свойство BackgroundColor используется для задания свойства BackgroundColor объекта StackLayout, а свойства Entry, DatePicker, Switch и Stepper привязаны к другим свойствам в модели представления.The BackgroundColor property is used to set the BackgroundColor property of the StackLayout, and the Entry, DatePicker, Switch, and Stepper properties are all bound to other properties in the ViewModel.

Свойство ItemsSource объекта ListView получает статическое свойство NamedColor.All.The ItemsSource property of the ListView is set to the static NamedColor.All property. Это заполняет объект ListView всеми экземплярами NamedColor.This fills the ListView with all the NamedColor instances. Для каждого элемента в ListView контекст привязки для элемента устанавливается для объекта NamedColor.For each item in the ListView, the binding context for the item is set to a NamedColor object. Объекты BoxView и Label в ViewCell привязаны к свойствам в NamedColor.The BoxView and Label in the ViewCell are bound to properties in NamedColor.

Свойство SelectedItem объекта ListView имеет тип NamedColor и привязывается к свойству BackgroundNamedColor объекта SampleSettingsViewModel:The SelectedItem property of the ListView is of type NamedColor, and is bound to the BackgroundNamedColor property of SampleSettingsViewModel:

SelectedItem="{Binding BackgroundNamedColor, Mode=TwoWay}"

Режим привязки по умолчанию для SelectedItem — OneWayToSource, и он устанавливает свойство модели представления из выбранного элемента.The default binding mode for SelectedItem is OneWayToSource, which sets the ViewModel property from the selected item. Режим TwoWay позволяет инициализировать SelectedItem из модели представления.The TwoWay mode allows the SelectedItem to be initialized from the ViewModel.

Но если SelectedItem устанавливается таким образом, ListView не прокручивается автоматически для отображения выбранного элемента.However, when the SelectedItem is set in this way, the ListView does not automatically scroll to show the selected item. Необходимо немного кода в файле с выделенным кодом:A little code in the code-behind file is necessary:

public partial class SampleSettingsPage : ContentPage
{
    public SampleSettingsPage()
    {
        InitializeComponent();

        if (colorListView.SelectedItem != null)
        {
            colorListView.ScrollTo(colorListView.SelectedItem,
                                   ScrollToPosition.MakeVisible,
                                   false);
        }
    }
}

На снимке экрана iOS слева показана программа при первом запуске.The iOS screenshot at the left shows the program when it's first run. Конструктор в SampleSettingsViewModel инициализирует белый цвет фона, как это выбрано в ListView:The constructor in SampleSettingsViewModel initializes the background color to white, and that's what's selected in the ListView:

Примеры параметровSample Settings

На другом снимке экрана показаны измененные параметры.The other screenshot shows altered settings. Экспериментируя с этой страницей, не забудьте перевести программу в спящий режим или завершить ее работу на устройстве или эмуляторе, где она выполняется.When experimenting with this page, remember to put the program to sleep or to terminate it on the device or emulator that it's running. Завершение программы из отладчика Visual Studio не приведет к тому, что OnSleep переопределит класс App для вызова.Terminating the program from the Visual Studio debugger will not cause the OnSleep override in the App class to be called.

В следующей статье будет показано, как указать форматирование строки привязок данных, заданных для свойства Text объекта Label.In the next article you'll see how to specify String Formatting of data bindings that are set on the Text property of Label.