Xamarin.Forms 绑定模式Xamarin.Forms Binding Mode

下载示例 下载示例Download Sample Download the sample

上一篇文章中,“替代代码绑定”和“替代 XAML 绑定”页面都有一个 Label,其 Scale 属性绑定到 SliderValue 属性 。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,所以这导致 LabelScale 属性被设置为 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. 绑定引用 LabelOpacity 属性,其默认值为 1。The binding references the Opacity property of the Label, which has a default value of 1.

正如你所料,SliderLabel 的初始 Opacity 值初始化为值 1。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

但是,正如 Android 和 UWP 屏幕截图所示,Slider 会继续工作,你可能会对此感到惊讶。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)

每个可绑定属性都有一个默认绑定模式,该模式在创建可绑定属性时进行设置,并且可从 BindableProperty 对象的 DefaultBindingMode 属性中获得。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.

大多数属性(如 RotationScaleOpacity)的默认绑定模式都是 OneWayThe 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.

但是,SliderValue 属性的默认绑定模式为 TwoWayHowever, 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. 这就是允许从初始 Opacity 值设置 Slider 的原因。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,但以下属性的默认绑定模式为 TwoWayMost bindable properties have a default binding mode of OneWay but the following properties have a default binding mode of TwoWay:

  • DatePickerDate 属性Date property of DatePicker
  • EditorEntrySearchBarEntryCellText 属性Text property of Editor, Entry, SearchBar, and EntryCell
  • ListViewIsRefreshing 属性IsRefreshing property of ListView
  • MultiPageSelectedItem 属性SelectedItem property of MultiPage
  • PickerSelectedIndexSelectedItem 属性SelectedIndex and SelectedItem properties of Picker
  • SliderStepperValue 属性Value property of Slider and Stepper
  • SwitchIsToggled 属性IsToggled property of Switch
  • SwitchCellOn 属性On property of SwitchCell
  • TimePickerTime 属性Time property of TimePicker

这些特定属性被定义为 TwoWay,理由非常充分:These particular properties are defined as TwoWay for a very good reason:

当数据绑定与模型-视图-视图模型 (MVVM) 应用程序体系结构一起使用时,ViewModel 类是数据绑定源,而由 Slider 等视图组成的 View 则是数据绑定目标。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. 你很可能会想要使用 ViewModel 中相应属性的值来初始化页面上的每个视图,但视图中的更改应该也会影响 ViewModel 属性。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

只读可绑定属性的默认绑定模式为 OneWayToSourceRead-only bindable properties have a default binding mode of OneWayToSource. 只有一个读/写可绑定属性的默认绑定模式为 OneWayToSourceThere is only one read/write bindable property that has a default binding mode of OneWayToSource:

  • ListViewSelectedItem 属性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

许多属性(包括 EntryIsTextPredictionEnabled 属性)都具有 OneTime 的默认绑定模式。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.

ViewModel 和属性更改通知ViewModels and Property-Change Notifications

“简易颜色选择器”页面演示了如何使用简单的 ViewModel 。The Simple Color Selector page demonstrates the use of a simple ViewModel. 数据绑定允许用户使用三个 Slider 元素为 Hue、Saturation 和 Luminosity 选择颜色。Data bindings allow the user to select a color using three Slider elements for the hue, saturation, and luminosity.

ViewModel 是数据绑定源。The ViewModel is the data-binding source. ViewModel 没有定义可绑定属性,但它实现了一种通知机制,允许在属性值更改时通知绑定基础结构 。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 类定义五个属性:HueSaturationLuminosityColor 属性是相互关联的。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 属性更改时,NamedColor 类中的静态 GetNearestColorName 方法(也包含在 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.

将 ViewModel 设置为绑定源时,绑定基础结构会将处理程序附加到 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 定义)的 BindingModeOneTime 时,绑定基础结构不必在 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. GridBindingContext 属性被设置为 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>

BoxViewLabel 和三个 Slider 视图从 Grid 继承绑定上下文。The BoxView, Label, and three Slider views inherit the binding context from the Grid. 这些视图都是引用 ViewModel 中的源属性的绑定目标。These views are all binding targets that reference source properties in the ViewModel. 对于 BoxViewColor 属性和 LabelText 属性,数据绑定是 OneWay:从 ViewModel 中的属性中设置视图中的属性。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.

但是,SliderValue 属性为 TwoWayThe Value property of the Slider, however, is TwoWay. 此模式允许从 ViewModel 设置每个 Slider,也允许从每个 Slider 设置 ViewModel。This allows each Slider to be set from the ViewModel, and also for the ViewModel to be set from each Slider.

首次运行程序时,BoxViewLabel 和三个 Slider 元素均根据实例化 ViewModel 时设置的初始 Color 属性集从 ViewModel 中进行设置。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

操纵滑块时,BoxViewLabel 会相应地更新,如 Android 和 UWP 屏幕截图所示。As you manipulate the sliders, the BoxView and Label are updated accordingly, as illustrated by the Android and UWP screenshots.

在资源字典中实例化 ViewModel 是一种常见的方法。Instantiating the ViewModel in the resource dictionary is one common approach. 也可以在 BindingContext 属性的属性元素标记内实例化 ViewModel。It's also possible to instantiate the ViewModel within property element tags for the BindingContext property. 在“简易颜色选择器”XAML 文件中,尝试从资源字典中删除 HslColorViewModel 并将其设置为 GridBindingContext 属性,如下所示 :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. 有时,代码隐藏文件会实例化 ViewModel 并将其设置为页面的 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

如果目标属性的默认绑定模式不适合特定的数据绑定,则可以通过将 BindingMode 属性(或 Binding 标记扩展的 Mode 属性)设置为 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 文件,以在绑定定义中包含 TwoWayFor 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. Slider 设置 TwoWay 绑定时,最初会从源设置 SliderWhen the TwoWay binding is set on the Slider, then the Slider is initially set from the source.

可以在“替代 XAML 绑定”示例中将绑定模式设置为 OneWayToSourceYou 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 类还定义了 ScaleXScaleY 属性,这些属性可以在水平和垂直方向上以不同方式缩放 VisualElementThe VisualElement class also defines ScaleX and ScaleY properties, which can scale the VisualElement differently in the horizontal and vertical directions.

使用 TwoWay 替代默认绑定模式的一种非常有用的应用程序涉及 ListViewSelectedItem 属性。A very useful application of overriding the default binding mode with TwoWay involves the SelectedItem property of ListView. 默认绑定模式为 OneWayToSourceThe default binding mode is OneWayToSource. 如果对 SelectedItem 属性设置数据绑定以引用 ViewModel 中的源属性,则从 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. 但是,在某些情况下,可能还需要从 ViewModel 初始化 ListViewHowever, in some circumstances, you might also want the ListView to be initialized from the ViewModel.

“示例设置”页面演示了此方法 。The Sample Settings page demonstrates this technique. 此页面表示应用程序设置的简单实现,这些设置通常在 ViewModel 中定义,例如此 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));
    }
}

每个应用程序设置都是一个属性,保存在名为 SaveState 的方法中的 Xamarin.Forms 属性字典中,并从该字典加载到构造函数中。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. 类的底部有两种方法可以帮助简化 ViewModel,并使它们不易出错。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 方法要更胜一筹:它将设置的属性值与存储为字段的值进行比较,并且仅当两个值不相等时才调用 OnPropertyChangedThe 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,并且该属性获取自 NamedColor 对象的 Color 属性。The BackgroundColor property is of type Color, and is obtained from the Color property of the NamedColor object.

NamedColor 类使用 .NET 反射来枚举 Xamarin.Forms Color 结构中的所有静态公共字段,并将它们以及它们的名称存储在可从静态 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;
    }
}

DataBindingDemos 项目中的 App 类定义类型为 SampleSettingsViewModel 且名为 Settings 的属性 。The App class in the DataBindingDemos project defines a property named Settings of type SampleSettingsViewModel. 在实例化 App 类时初始化此属性,并在调用 OnSleep 方法时调用 SaveState 方法: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. 使用 Binding 标记扩展来设置页面的 BindingContext:绑定源是静态 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 属性用于设置 StackLayoutBackgroundColor 属性,而 EntryDatePickerSwitchStepper 属性均被绑定到 ViewModel 中的其他属性。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.

ListViewItemsSource 属性被设置为静态 NamedColor.All 属性。The ItemsSource property of the ListView is set to the static NamedColor.All property. 这会使用所有 NamedColor 实例来填充 ListViewThis 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. ViewCell 中的 BoxViewLabel 被绑定到 NamedColor 中的属性。The BoxView and Label in the ViewCell are bound to properties in NamedColor.

ListViewSelectedItem 属性属于类型 NamedColor,并被绑定到 SampleSettingsViewModelBackgroundNamedColor 属性: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,它从所选项目中设置 ViewModel 属性。The default binding mode for SelectedItem is OneWayToSource, which sets the ViewModel property from the selected item. TwoWay 模式允许从 ViewModel 中初始化 SelectedItemThe 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 调试器终止程序不会导致调用 App 类中的 OnSleep 替代。Terminating the program from the Visual Studio debugger will not cause the OnSleep override in the App class to be called.

在下一篇文章中,可了解如何指定对 LabelText 属性设置的数据绑定的字符串格式In the next article you'll see how to specify String Formatting of data bindings that are set on the Text property of Label.