重要的事情说三遍:

  本文基本是取自微软官方 bindable properties, 官方也提供了,笔者只是尝试用自己的理解描述一遍,便于记忆。如有不对之处,欢迎拍砖。

  本文基本是取自微软官方 bindable properties, 官方也提供了,笔者只是尝试用自己的理解描述一遍,便于记忆。如有不对之处,欢迎拍砖。

  本文基本是取自微软官方 bindable properties, 官方也提供了,笔者只是尝试用自己的理解描述一遍,便于记忆。如有不对之处,欢迎拍砖。

 

概述

  首先来说说bindableproperty是什么?官方的说是:在xamarin.forms中,公共语言运行时(clr)的属性(properites)的功能由bindable properties来扩展。它是一种特殊的属性,特殊的点在于它的值是被xamarin.foms的属性系统跟踪的。

  它是通过使用备份一个bindableproperty的类型来实现扩展property的功能的,而不是使用备份field的方式。它的目标是提供这样一个属性系统,这个系统支持类似于父子关系的方式来设置数据绑定,样式更改等等。此外,它还额外提供了一些别的功能,如设置默认值,验证属性合法性,提供属性更改时的回调函数等等。

  上面两段内容确实很绕,换成笔者的理解是:利用bindableproperty来创建的属性不同于我们平常所使用的形如 public string abc { get; set; } 这样的普通属性,它额外提供了对数据绑定,以及相关方法的支持。这种功能是通过xamrin.forms这个框架自身对bindable properties的值的追踪来实现的。

  如果你有下面的某些功能的需求,那么你就应该使用bindable properties

  • 作为数据绑定的有效目标值。(注意这里是目标值(target),而不是源(source))
  • 通过样式来设置属性
  • 提供一个与该属性本身默认值不同的默认值。(如int的默认值是0,如果你想默认指定为别的值,可以使用bindable properties。原文是 providing a default property value that’s different from the default for the type of the property.
  • 验证属性的值
  • 监控这个属性值的变更

  举个例子,xamarin.forms自带的如 label.text,button.borderradius,stacklayout.orientation等等都是可绑定属性。任何一个可绑定的属性在其所在的class内都有一个由pubilc static readonly property修饰的bindableproperty对应。加粗的这句话不是很好理解,举个例子。如前面提到的xamarin.forms自带的lable.text是一个可绑定属性,那么在lable这个类中也一定包含这样的定义

public static readonly xamarin.forms.bindableproperty textproperty;

 

创建和实用可绑定属性

  可以使用如下的流程来创建可绑定属性:

  1. 利用 bindableproperty.create方法来创建一个bindableproperty的实例。(该方法包括多个重载)。
  2. 为第一步骤中创建的实例定义一个属性访问器。

  请注意,所有的可绑定属性都必须在ui thread上创建。这意味着只有跑在ui thread上的代码可以获取(get)或改变(set)可绑定属性的值。然而,可以通过使用device.begininvokeonmainthread方法将可绑定属性的实例封送到ui thread,来实现从其他线程访问。(这句的翻译不是很准确,原文是:however, bindableproperty instances can be accessed from other threads by marshaling to the ui thread with the device.begininvokeonmainthread method.)

  创建

  包含bindableproperty属性的类必须派生自bindableobject,但bindableobject继承关系中处于较高的位置,因此大部分用于ui的类都支持可绑定属性。

  通过使用public static readonly来声明一个可绑定属性,使用bindableproperty.create的某个重载来创建它。此处要注意可绑定属性应该定义在bindableobject派生类的主体内,并且在该类任何成员的外部。

  创建可绑定属性时至少应该指定下面所需的参数:

  • 可绑定属性的name
  • 该属性的类型(the type of the property)
  • 包含整个属性的类的类型(the type of the owning object)
  • 默认值。这个默认值可设置为属性本身的默认值不同,如创建一个int类型的可绑定属性可将这里的默认值设置为5,而不是使用int自身的默认值0。此外,当调用clearvalue方法时也将会把值重置为我们定义的默认值。

  例如:

public static readonly bindableproperty eventnameproperty =
  bindableproperty.create ("eventname", typeof(string), typeof(eventtocommandbehavior), null);

  这段代码将创建一个名为“eventname”,string类型的可绑定属性的实例。它属于eventtocommandbehavior这个类,并且默认值是null。可绑定属性的命名规范要求其必须与create方法中所指定的name相匹配,并在后面追加”property”。如上面的代码,eventname与eventnameproperty。

  此外,在创建bindableproperty的实例时,还可以指定以下的一些参数

  • 绑定模式,这里不用多说了,就是绑定中所对应的那几种模式。这里的默认值是source to target。
  • 设置属性时所使用的验证方法的委托。
  • 属性更改完成时所调用方法的委托。
  • 属性更改将要发生时所调用的方法的委托,此方法与更改完成时的方法具有相同的签名。
  • 属性更改时调用的“强制值”委托。(a coerce value delegate that will be invoked when the property value has changed.)
  • 用于初始化默认属性值的func委托。

  创建属性访问器

  需要使用属性语法(property syntax)来创建可用于访问 可绑定属性 的属性访问器。get方法需要利用getvalue从对应的bindableproperty中获取值,再使用类型转换拿到所需的值。set方法需要使用setvalue方法来为其复制。例如

public string eventname {
  get { return (string)getvalue (eventnameproperty); }
  set { setvalue (eventnameproperty, value); }
}

  使用可绑定属性

  一旦声明了可绑定属性就可以在xaml和c#代码中使用它。在xaml中,还需要先声明带有前缀的命名空间,这个空间中包含着我们创建的可绑定属性所属的类。在xaml中

  声明

<contentpage ... xmlns:local="clr-namespace:eventtocommandbehavior" ...>
  ...
</contentpage>

  使用

<listview ...>
  <listview.behaviors>
    <local:eventtocommandbehavior eventname="itemselected" ... />
  </listview.behaviors>
</listview>

  等效的c#代码

var listview = new listview ();
listview.behaviors.add (new eventtocommandbehavior {
  eventname = "itemselected",
  ...
});

 

进阶使用场景

  检测属性更改

  在利用bindableproperety.create方法创建可绑定属性的实例时可以通过propertychanged参数来注册一个静态的回调方法,该方法会在可绑定属性的值发生变更时执行。例如:

public static readonly bindableproperty eventnameproperty =
  bindableproperty.create (
    "eventname", typeof(string), typeof(eventtocommandbehavior), null, propertychanged: oneventnamechanged);
...

static void oneventnamechanged (bindableobject bindable, object oldvalue, object newvalue)
{
  // property changed implementation goes here
}

  oneventnamechanged方法中的参数bindableobject bindable用于指定是哪个实例在执行这个方法,后面两个参数就不解释了。

  用于验证属性的回调函数

  注册方法与上面类似,直接贴代码

public static readonly bindableproperty angleproperty =
  bindableproperty.create ("angle", typeof(double), typeof(homepage), 0.0, validatevalue: isvalidvalue);
...

static bool isvalidvalue (bindableobject view, object value)
{
  double result;
  bool isdouble = double.tryparse (value.tostring (), out result);
  return (result >= 0 && result <= 360);
}

  验证属性的这个方法会根据接收到的参数的值以及你定义好的逻辑来判断它是否通过验证。如果没有通过验证则返回false,开发需要处理这种情况。(如ui上style的变更,以提示用户输入值不满足要求等等)。如上面代码中的例子,被验证的值只有在处于[0,360]才能验证通过。

  强制值回调(coerce value callback)

  注册方法与上面类似,直接贴代码

public static readonly bindableproperty angleproperty = bindableproperty.create (
  "angle", typeof(double), typeof(homepage), 0.0, coercevalue: coerceangle);
public static readonly bindableproperty maximumangleproperty = bindableproperty.create (
  "maximumangle", typeof(double), typeof(homepage), 360.0);
...

static object coerceangle (bindableobject bindable, object value)
{
  var homepage = bindable as homepage;
  double input = (double)value;

  if (input > homepage.maximumangle) {
    input = homepage.maximumangle;
  }

  return input;
}

  这里需要说明一下。coercevalue方法会在属性值发生变更时调用。它用于在属性值变更时重新改变属性的值。如上面的代码所示,可以使用它来确保一个可绑定属性的值不大于另一个可绑定属性的值。coerceangle方法检查maximumangle属性的值,如果angle属性值大于它,则它会将值强制转换为maximumangle属性值。

  使用func创建默认值

  贴代码

public static readonly bindableproperty sizeproperty =
  bindableproperty.create ("size", typeof(double), typeof(homepage), 0.0,
  defaultvaluecreator: bindable => device.getnamedsize (namedsize.large, (label)bindable));

  此处使用一个device.getnamedsize方法来获取在原生平台(native platform)上lable的字体大小,并将其设置为sizeproperty的默认值。