事件和事件处理在任何编程开发过程中都是一件特别繁琐的事情。Xamarin.From中提供的Data binding特性,可以将两个对象的属性自动关联起来,从而极大地简化事件处理的流程。此外,Data binding还是MVVM(Model-View-ViewModel)应用架构中非常核心的角色,是实现显示与数据处理分离的关键特性。本文,我就与大家一起来学习Data binding的相关知识。

基础知识

Data binding 相关的类、属性及方法:

  • Binding类,继承自BindingBase,定义了数据绑定的许多特性;
  • BindingContext属性,由BindableObject定义;
  • SetBinding方法,由BindableObject定义;
  • BindableObjectExtensions类中定义了两个SetBinding方法的重载;

支持XAML标志扩展的两个类:

  • BindingExtension类,为Xamarin.Forms私有,提供在XAML中使用Binding标识进行数据绑定的支持;
  • ReferenceExtension类,这也是数据绑定的关键类;

与Data binding相关的两个接口:

  • INotifyPropertyChanged(在System.ComponentModel命名空间下),用于在属性发生变化时发送通知的标准接口;
  • IValueConverter(在Xamarin.Forms 命名空间下),用于在数据绑定中的类型转换;

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

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

对源则没有这样的要求,因此源可以是一个普通的C#属性;但在一般的数据绑定中,源的变化应当引起目标的变化,这种变化需要某种通知机制进行传递。这里有一个现成的机制—— INotifyPropertyChanged接口。INotifyPropertyChanged接口的内容非常简单,仅仅定义了个PropertyChanged事件,这个事件在属性变化时会被触发;

public interface INotifyPropertyChanged {
    event PropertyChangedEventHandler PropertyChanged; 
}

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

而BindableObject正好就实现了INotifyPropertyChanged接口,因此,只需将源定义成BindableObject对象即可,这样它既可以成为源,也可以成为目标。而只实现INotifyPropertyChanged就只能作为源,不过,这么做也有好处,就是在实现上更简单,我们后面再介绍只使用INotifyPropertyChanged的做法。


BindableObject和BindableProperty

那么BindableObject和BindableProperty是什么关系呢?

** BindableObject提供了对BindableProperty对象的支持,而BindableProperty是对CLR属性的扩展。**

所谓的CLR属性即是我们一般情况下使用的C#属性,CLR是Common Language Runtiome通用语言运行时的缩写。

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

以最简单的Label为例,Label是一个BindableObject,其Text属性是一个可绑定的CLR属性。 我们一般是这样使用Text属性的。

Label label1 = new Label();
label1.Text = "Some Text";

但其实下面的做法是等效的:

Label label2 = new Label();
label2.SetValue(Label.TextProperty, "Some Text");

因为Text属性的本质是这样的:

public string Text
{ 
 set { SetValue(Label.TextProperty, value); }
 get { return (string)GetValue(Label.TextProperty); 
}

其中SetValue和GetValue都是BindableObject中定义的方法。Label.TextProperty则一个BindableProperty对象。

//TextProperty在Label类中的声明
public static readonly BindableProperty TextProperty;

可见,关于Text属性的所有工作都是通过SetValue和GetValue完成的。而对Text的调用本质上都是对Label.TextProperty的调用,而Label.TextProperty为Text提供了一整套进行数据绑定的机制,因此Text也就成为了可绑定的属性。

那么下面我们说一说如何定义BindableProperty对象。

假设我们要为一个类增加一个可绑定的TestProperty属性,写法大致如下:

 public class MyClass : BindableObject
      {
         public static readonly BindableProperty TestProperty 
                                                =BindableProperty.Create("Test", //属性名称 propertyName
                                                typeof(int),     //返回类型 returnType
                                                typeof(MyClass), // 声明者的类型 declaringType
                                                8, //默认值 defaultValue
                                                ...);
         ...
         public int Test //同上面的属性名称一致
         {
           set { SetValue(TestProperty, value); }
           get { return (int)GetValue(TestProperty); }
         }
         ...
}

BindableProperty.Create方法其实还有6个可选参数:

其中defaultBindingMode是默认的绑定模式;validateValue是检查值合法性的回调方法;propertyChanged和propertyChanging分别是值发生改变后和正上改变时触发的回调方法;coerceValue是在值发生改变时,可以对值进行处理的回调方法;这三个方法的发生顺序是coerceValue-》propertyChanging-》propertyChanged。而defaultValueCreator是可以根据需要提供不同默认值的回调方法,当然这个方法只发生在绑定的最开始。

其使用方法很简单,假使现在有一个TestBindingable :ContentPage,我们定义的XAML如下:

...
<StackLayout>
        <Label
            Text="Text"
            x:Name="label" />
        <Button
            Text="button_click"
            Clicked="button_click" />
    </StackLayout>
...

在C#代码文件中,

...
MyClass myClass = new MyClass();
public TestBindingable() {
    InitializeComponent();
    label.BindingContext = myClass;
    label.SetBinding(Button.FontSizeProperty, "Test");
}

void button_click(object sender, System.EventArgs e) {
    myClass.Test += 2;
}
...

运行之后,我们每点击一次button_click按钮,就会发现Label中的字体变大一点,可见Label的FontSize属性与myClass.Test绑定在了一起。

其它的,我们下一篇再继续介绍吧。