Xamarin.Forms数据绑定

 

生命周期

在 Android 上,若主活动的 [Activity()] 属性缺少 ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,旋转时及首次启动应用程序时,将调用 OnStart 方法。

 

数据绑定

数据绑定在用户界面和应用程序之间建立连接。

官网:https://docs.microsoft.com/zh-cn/xamarin/xamarin-forms/app-fundamentals/data-binding/

原理参考:Xamarin.From中的Data binding(数据绑定)(一)

基本绑定

数据绑定连接两个对象,即源和目标。 源对象提供数据, 目标对象使用(并经常显示)来自源对象的数据。 例如, Editor (目标对象) 通常会将Text属性绑定到对象中string的属性。 下图说明了这种绑定关系:

数据绑定的主要优点是让你无需再担心视图和数据源之间的数据同步。 底层的绑定框架源会将源对象中的更改自动推送到目标对象,且目标对象中的更改可选择性地推送回源对象。

建立数据绑定的过程分为两个步骤:

  • 目标对象的 BindingContext 属性必须设置为源。
  • 必须在目标和源之间建立绑定。 

实现绑定有两种方式:仅在xaml文件中或者仅在cs中,每种又分使用BindingContext与否。

1、在 XAML 中设置【常用方式】

不使用BingingContext通过使用 Binding 标记扩展实现。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.AlternativeXamlBindingPage"
             Title="Alternative XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="40"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               Scale="{Binding Source={x:Reference slider},
                               Path=Value}" />

        <Slider x:Name="slider"
                Minimum="-2"
                Maximum="2"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
View Code

使用BingingContext:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BasicXamlBindingPage"
             Title="Basic XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               BindingContext="{x:Reference Name=slider}"
               Rotation="{Binding Path=Value}" />

        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
View Code

XAML标记扩展(例如x:Reference和Binding)可以定义内容属性特性,对于XAML标记扩展,这意味着不需要显示属性名称。

Name属性是x:Reference的content属性,Path属性是Binding的content属性,这意味着可以从表达式中消除它们:

<Label Text="TEXT"
       FontSize="80"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       BindingContext="{x:Reference slider}"
       Rotation="{Binding Value}" />
View Code

同时,源属性是由 BindingExtension 的 Path 属性指定的,它对应于 Binding 类的 Path 属性(Binding 标记扩展的内容属性是 Path,但是标记扩展的 Path= 部分只有在它是表达式中的第一个属性时才能被删除。)

总结最精简做法:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.AlternativeXamlBindingPage"
             Title="Alternative XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="40"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               Scale="{Binding Value, Source={x:Reference slider}}" />

        <Slider x:Name="slider"
                Minimum="-2"
                Maximum="2"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
View Code

小结:

将Binding的Source属性或者BindingContext属性设置为x:Reference标记扩展,以引用页面上的另一个视图(源对象)。 这两个属性的类型为Object,可以将它们设置为任何包含适合于绑定源的属性的对象。

如果两者都已设置,则 Binding 的 Source 属性优先于 BindingContext

2、只在cs中设定

需要设置以下

public BasicCodeBindingPage()
    {
        InitializeComponent();

        label.BindingContext = slider;
        label.SetBinding(Label.RotationProperty, "Value");
    }

 

绑定上下文继承

父布局中定义了源对象,子控件中都可以使用。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BindingContextInheritancePage"
             Title="BindingContext Inheritance">
    <StackLayout Padding="10">

        <StackLayout VerticalOptions="FillAndExpand"
                     BindingContext="{x:Reference slider}">

            <Label Text="TEXT"
                   FontSize="80"
                   HorizontalOptions="Center"
                   VerticalOptions="EndAndExpand"
                   Rotation="{Binding Value}" />

            <BoxView Color="#800000FF"
                     WidthRequest="180"
                     HeightRequest="40"
                     HorizontalOptions="Center"
                     VerticalOptions="StartAndExpand"
                     Rotation="{Binding Value}" />
        </StackLayout>

        <Slider x:Name="slider"
                Maximum="360" />

    </StackLayout>
</ContentPage>
View Code

绑定模式

默认绑定模式

使用 BindingMode 枚举的成员指定绑定模式:

  • Default
  • TwoWay – 数据在源和目标之间双向传输
  • OneWay – 数据从源到目标单向传输
  • OneWayToSource – 数据从目标到源单向传输
  • OneTime – 只有在 BindingContext 更改时,数据才从源到目标单向传输(Xamarin.Forms 3.0 新增功能)

每个可绑定属性都有一个默认绑定模式,该模式在创建可绑定属性时进行设置,并且可从 BindableProperty 对象的 DefaultBindingMode 属性中获得。 此默认绑定模式指示该属性是数据绑定目标时有效的模式。

大多数属性(如 RotationScale 和 Opacity)的默认绑定模式都是 OneWay。 如果这些属性是数据绑定目标时,则从源设置目标属性。

但是,Slider 的 Value 属性的默认绑定模式为 TwoWay。 这意味着,如果 Value 属性是数据绑定目标时,则通常从源设置目标,但源也可从目标设置。 这就是允许从初始 Opacity 值设置 Slider 的原因。

【<!--Slider.Value是twoWay模式,它的Value 是从Label的opacity处来,默认是1。 同时Label的opacity也是1,改变value时,Label的opacity也会改变-->】

这种双向绑定似乎会创建一个无限循环,但这种情况不会发生。 除非属性实际发生变化,否则可绑定属性不会发出属性更改的信号,这样可以避免无限循环。

1、双向绑定

大多数可绑定属性的默认绑定模式都是 OneWay,但以下属性的默认绑定模式为 TwoWay

  • DatePicker 的 Date 属性
  • EditorEntrySearchBar 和 EntryCell 的 Text 属性
  • ListView 的 IsRefreshing 属性
  • MultiPage 的 SelectedItem 属性
  • Picker 的 SelectedIndex 和 SelectedItem 属性
  • Slider 和 Stepper 的 Value 属性
  • Switch 的 IsToggled 属性
  • SwitchCell 的 On 属性
  • TimePicker 的 Time 属性

这些特定属性被定义为 TwoWay,理由非常充分:

当数据绑定与模型-视图-视图模型 (MVVM) 应用程序体系结构一起使用时,ViewModel 类是数据绑定源,而由 Slider 等视图组成的 View 则是数据绑定目标。 MVVM 绑定更类似于“ReverseBindingPage”示例,而不是之前示例中的绑定 。 你很可能会想要使用 ViewModel 中相应属性的值来初始化页面上的每个视图,但是视图中的更改也将影响ViewModel属性。

默认绑定模式为 TwoWay 的属性 是最有可能在 MVVM 方案中使用的属性。

2、OneWayToSource绑定

只读可绑定属性的默认绑定模式为 OneWayToSource【目标到源】。 

只有一个读/写可绑定属性的默认绑定模式为 OneWayToSource

  • ListView 的 SelectedItem 属性

其基本原理是,对 SelectedItem 属性的绑定(改变)应该导致 重新设置绑定源。

3、一次性绑定

许多属性(包括 Entry 的 IsTextPredictionEnabled 属性)都具有 OneTime 的默认绑定模式。

只有在绑定上下文更改时,才会更新绑定模式为 OneTime 的目标属性。 对于这些目标属性的绑定,该模式简化了绑定基础结构,因为不必监视源属性中的更改。

ViewModels and 属性更改通知

ViewModel的使用,参考:C#使用Xamarin开发可移植移动应用(4.进阶篇MVVM双向绑定和命令绑定)附源码

Data binding的核心思想:Data binding总是有一个源(source)和一个目标(target);源是某个对象的一个属性,通常会在运行期间发生变化;当这个属性变化时,Data binding将自动将这一变化更新到目标上,即另一个对象的一个属性上。

有一点特别重要:Data binding中的目标必须是一个BindableProperty对象

在Xamarin.Form中,大多数的View、Layout和Page都是BindableObject对象。

对源则没有这样的要求,因此源可以是一个普通的C#属性ViewModel 类是数据绑定源,在ViewModel中定义C#属性。。

但在一般的数据绑定中,源的变化应当引起目标的变化,这种变化需要某种通知机制进行传递。这里有一个现成的机制—— INotifyPropertyChanged接口。

INotifyPropertyChanged接口的内容非常简单,仅仅定义了个PropertyChanged事件,这个事件在属性变化时会被触发;

public interface INotifyPropertyChanged {
    event PropertyChangedEventHandler PropertyChanged; 
}

因此,Data binding中的源必须实现INotifyPropertyChanged接口

而BindableObject正好就实现了INotifyPropertyChanged接口,因此,只需将源定义成BindableObject对象即可,这样它既可以成为源,也可以成为目标。

而只实现INotifyPropertyChanged就只能作为源,不过,这么做也有好处,就是在实现上更简单。

覆盖默认的绑定模式

如果目标属性的默认绑定模式不适合特定的数据绑定,则可以通过将 Binding 的 Mode 属性(或 Binding 标记扩展的 Mode 属性)设置为 BindingMode 枚举的其中一个成员来替代它。

但是,将 Mode 属性设置为 TwoWay 并不总是像你预期的那样有效。

替换掉默认的绑定模式的例子:SampleSettingsViewModel
一种非常有用的应用程序涉及 ListView 的 SelectedItem 属性。,其默认绑定模式为 OneWayToSource(目标到源)
我们将其改为TwoWay。

字符串格式设置

在代码中显示字符串时,最强大的工具是静态 String.Format 方法。 格式设置字符串包括特定于各种类型的对象的格式代码,也可以包括其他文本以及正在进行格式设置的值。 有关字符串格式的详细信息,请参阅设置 .NET 中类型的格式一文。

<Slider x:Name="slider" />
<Label Text="{Binding Source={x:Reference slider},
                      Path=Value,
                      StringFormat='The slider value is {0:F2}'}" />

Path的值会去替换占位符{0}。

绑定路径

前面将Binding 类的 Path 属性(或 Binding 标记扩展的 Path 属性)已设置为单个属性。 实际上,可以将 Path 设置为“子属性”(属性的属性),也可以设置为集合的成员 。

示例中有一些很有用的说明。

如果绑定路径中的属性没有实现 INotifyPropertyChanged,那么对该属性的任何更改都将被忽略。 一些更改可能会使绑定路径完全无效,所以应只在属性和子属性字符串永远不会失效的情况下才使用这种技术。

绑定值转换器

  • 说明

当源和目标属性都是同一类型,或当一个类型可以隐式转换为另一种类型时,这类传递都是非常简单的。 如果不是这种情况,则必须执行类型转换。

字符串格式设置一文已介绍如何使用数据绑定的 StringFormat 属性将任意类型转换为字符串 。 对于其他类型的转换,需要在类中编写一些专门的代码以实现 IValueConverter 接口。 (通用 Windows 平台在 Windows.UI.Xaml.Data 命名空间中包含一个名为 IValueConverter 的类似的类,但此 IValueConverter 在 Xamarin.Forms 命名空间中。)实现 IValueConverter 的类被称为“值转换器”,但它们通常也被称为“绑定转换器”或“绑定值转换器” 。

  • 实例

想要定义源属性为类型 int 但目标属性为 bool 的数据绑定。 想要此数据绑定在整数源等于 0 时生成 false 值,在其他情况则生成 true

public class IntToBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)value != 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? 1 : 0;
    }
}
View Code

将此类的实例设为 Binding 类的 Converter 属性,此类即成为数据绑定的一部分。

当数据在 OneWay 或 TwoWay 绑定中由源移动到目标时,将调用 Convert 方法。 value 是来自数据绑定源的对象或值,该方法必须返回数据绑定目标类型的值。 此处所示的方法将 value 参数强制转换为 int,然后将其与 0 比较并得到 bool 返回值。

当数据在 TwoWay 或 OneWayToSource 绑定中由目标移动到源时,将调用 ConvertBack 方法。 ConvertBack执行相反的转换:它假定 value 参数是来自目标的 bool,然后将其转换为源的 int 返回值。

xaml中使用:

<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.EnableButtonsPage"
             Title="Enable Buttons">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:IntToBoolConverter x:Key="intToBool" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Padding="10, 0">
        <Entry x:Name="entry1"
               Text=""
               Placeholder="enter search term"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Search"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                IsEnabled="{Binding Source={x:Reference entry1},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />

        <Entry x:Name="entry2"
               Text=""
               Placeholder="enter destination"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Submit"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                IsEnabled="{Binding Source={x:Reference entry2},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
    </StackLayout>
</ContentPage>
View Code

 

绑定回退

有时数据绑定会失败,因为无法解析绑定源,或者因为绑定成功但返回 null 值。 虽然可以使用值转换器或其他附加代码处理这些情况,但是通过定义在绑定过程失败时要使用的回退值,可以使数据绑定更加可靠。 这可以通过定义绑定表达式中的 FallbackValue 和 TargetNullValue 属性来实现。 因为这些属性位于 BindingBase 类中,它们可以与绑定、编译绑定和 Binding 标记扩展一起使用。

命令接口

在“模型-视图-视图模型”(即 MVVM)体系结构中,数据绑定是在 ViewModel(通常是派生自 INotifyPropertyChanged 的类)中的属性和视图(通常为 XAML 文件)中的属性之间定义的。 

有时,应用程序的需求超越了属性绑定层面,它要求用户启动影响 ViewModel 中某些内容的命令。 这些命令通常通过点击按钮或手指敲击触发信号,往往是以下两个事件的处理程序中的代码隐藏文件中处理它们:Button 的 Clicked 事件或 TapGestureRecognizer 的 Tapped 事件。

命令接口提供了另一种实现命令的方法,这种方法更适合 MVVM 体系结构。 ViewModel 本身可以包含命令,这些命令是针对视图中的特定活动(例如单击 Button)而执行的方法。 在这些命令和 Button 之间定义了数据绑定。

为允许使用 Button 和 ViewModel 之间的绑定数据,Button 定义两个属性:

要使用命令接口,您需要定义一个针对Button的Command属性的数据绑定,其中源是ViewModel中ICommand类型的属性。 ViewModel包含与单击按钮时执行的与ICommand属性关联的代码。 您可以将CommandParameter设置为任意数据,以区分多个按钮(如果它们都绑定到ViewModel中的同一ICommand属性)。

下列类也定义了 Command 和 CommandParameter 属性:

SearchBar 定义一个 ICommand 类型的 SearchCommand 属性和一个 SearchCommandParameter 属性。 ListView 的 RefreshCommand 属性也是 ICommand 类型。

可以在 ViewModel 中以不依赖视图中的特定用户界面对象的方式处理上述所有命令。

使用:

若要使用命令接口,ViewModel 需包含 ICommand 类型的属性(用于处理View(eg 按钮)的所有逻辑)

ICommand 接口【System.Windows.Input.ICommand】由两个方法和一个事件组成:Execute、CanExecute、CanExecuteChanged。

而一般用位于Xamarin.Forms的的Command类来实现ICommand接口类型的属性。

通过 Command 类的构造函数,你可以传递与 Execute 和 CanExecute 方法对应的 Action 和 Func<bool> 类型的参数。

参考实例:PersonEntryPage 【主要是按钮的Command属性的绑定】

它的工作原理如下:用户首先按“新建”按钮 。 这将启用输入窗体,但禁用“新建”按钮 。 然后用户输入姓名、年龄和技能。 在编辑过程中,用户随时都可以按下“取消”按钮重新开始 。 只有在输入了姓名和有效年龄后,才启用“提交”按钮 。 按“提交”按钮可将人员转移到 ListView 显示的集合中 。 按“取消”或“提交”按钮后,会清除输入窗体中的内容并再次启用“新建”按钮 。

注:如果使用命令接口,请勿使用 Button 的 IsEnabled 属性。

1、Excute

用户按下 Button 时,Button 调用绑定到 Button的Command 属性的 ICommand 对象中的 Execute 方法,即上面的Action execute 无返回值的委托。

2、CanExcute

首次在 Button 的 Command 属性上定义绑定时【即初始加载时】,以及数据绑定以某种方式更改时Button 调用 ICommand 对象中的 CanExecute 方法。 

如果 CanExecute 返回 false,则 Button 将禁用其自身。 这表示特定命令当前不可用或无效。

3、ChangeCanExecute 

每当发生任何可能更改 CanExecute 方法返回值【eg 按钮是否可用】的事情时,ViewModel 都应该为 ICommand 属性调用 ChangeCanExecute。【自定义的RefreshCanExecutes()方法中的设定】

调用 ChangeCanExecute方法 将导致 Command 类触发 CanExecuteChanged 事件。 示例中Button 已为该事件附加了一个处理程序,并通过再次调用 CanExecute 进行响应,然后根据该方法的返回值启用自身。

 

注:示例中的提交按钮

因为最初单击了New按钮,执行了RefreshCanExecutes()方法,三个按钮的CanExecuteChanged 事件均会附加处理程序。

每当编辑中的 PersonViewModel 对象中的属性发生更改时,都会调用 SubmitCommand 的 canExecute 函数。即实时 判断提交按钮是否可用。。

 

 

已编译的绑定

已编译绑定的解析速度快于传统绑定解析,因而可提升 Xamarin.Forms 应用程序中的数据绑定的性能 。

数据绑定存在两个主要问题:

  1. 绑定表达式不具有编译时验证。 相反,绑定在运行时解析。 因此,在应用程序未按预期运行或出现错误消息时,直到运行时才会检测到任何无效绑定。
  2. 它们不具有成本效益。 使用常规对象检查(反射)在运行时解析绑定,并且执行此操作的开销因平台而异。

已编译的绑定通过在编译时而不是运行时解析绑定表达式来提升 Xamarin.Forms 应用程序中的数据绑定性能。 此外,绑定表达式的这种编译时验证可以提供更好的开发人员故障排除体验,因为无效绑定会被报告为生成错误。

使用已编译的绑定的过程:

  1. 启用 XAML 编译。 有关 XAML 编译的详细信息,请参阅 XAML 编译
  2. 将 VisualElement 上的 x:DataType 属性设置为 VisualElement 及其子元素将绑定到的对象类型。 请注意,可以在视图层次结构中的任意位置重新定义此属性。

在 XAML 编译时,会将任何无效绑定表达式报告为生成错误。 但是,XAML 编译器仅报告遇到的第一个无效绑定表达式的生成错误。 

设置程序集级别的xaml编译(AssemblyInfo.cs文件中):[assembly: XamlCompilation(XamlCompilationOptions.Compile)]

无论是在 XAML 还是代码中设置 BindingContext,都将编译在 VisualElement 或其子元素上定义的任何有效绑定表达式。 编译绑定表达式会生成编译代码,该代码将从源上的属性获取值,并将在标记中指定的目标的属性上对其进行设置 。 此外,根据绑定表达式,生成的代码可能会观察到源属性值的更改并刷新目标属性,并可能会将更改从目标推送回源 。

注:目前,定义 Source 属性的任何绑定表达式都禁用了编译绑定。 这是因为 Source 属性始终使用 x:Reference 标记扩展进行设置,该设置在编译时无法解析。

 

posted @ 2019-10-15 18:27  peterYong  阅读(3165)  评论(0编辑  收藏  举报