定义可绑定属性
假设您想要一个增强的Label类,它允许您以点为单位指定字体大小。 让我们将这个类AltLabel称为“替代标签”。它派生自Label并包含一个名为PointSize的新属性。
PointSize应该由可绑定属性支持吗? 当然! (虽然这样做的真正好处直到下一章才会得到证明。)
仅代码的AltLabel类包含在Xamarin.FormsBook.Toolkit库中,因此可以访问多个应用程序。 新的PointSize属性是使用名为PointSizeProperty的BindableProp?erty对象和引用PointSizeProperty的名为PointSize的CLR属性实现的:
public class AltLabel : Label
{
public static readonly BindableProperty PointSizeProperty … ;
public double PointSize
{
set { SetValue(PointSizeProperty, value); }
get { return (double)GetValue(PointSizeProperty); }
}
}
字段和属性定义都必须是公共的。
因为PointSizeProperty被定义为static和readonly,所以必须在静态构造函数中或在字段定义中将其赋值,之后才能更改它。 通常,通过使用静态BindableProperty.Create方法在字段定义中指定BindableProperty对象。 需要四个参数(此处显示参数名称):
- propertyName属性的文本名称(在本例中为“PointSize”)
- returnType属性的类型(本例中为double)
- declaringType定义属性的类的类型(AltLabel)
- defaultValue默认值(比方说8分)
第二个和第三个参数通常使用typeof表达式定义。 这是赋值语句,将这四个参数传递给BindableProperty.Create:
public class AltLabel : Label
{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create("PointSize", // propertyName
typeof(double), // returnType
typeof(AltLabel), // declaringType
8.0, // defaultValue
);
}
请注意,默认值指定为8.0而不是8.因为BindableProp?erty.Create旨在处理任何类型的属性,所以defaultValue参数定义为object。 当C#编译器只遇到8作为该参数时,它将假定8是一个int并将int传递给该方法。 直到运行时才会显示该问题,但是,当BindableProperty.Create方法期望默认值为double类型并通过引发TypeInitializationException进行响应时。
您必须明确指定要指定为默认值的值的类型。 不这样做是定义可绑定属性的一个非常常见的错误。 一个非常常见的错误。
BindableProperty.Create还有六个可选参数。 这里他们是参数名称及其目的:
- defaultBindingMode与数据绑定结合使用
- validifyValue用于检查有效值的回调
- propertyChanged一个回调,指示属性何时发生更改
- propertyChanging一个回调,指示属性何时将要更改
- coerceValue将设置值强制转换为另一个值的回调(例如,将值限制为某个范围)
- defaultValueCreator用于创建默认值的回调。 这通常用于实例化一个不能在类的所有实例之间共享的默认对象; 例如,一个集合对象,如List或Dictionary。
不要在CLR属性中执行任何验证,强制或属性更改处理。 CLR属性应限制为SetValue和GetValue调用。 其他所有内容都应该在可绑定属性基础结构提供的回调中完成。
对BindableProperty.Create的特定调用很少需要所有这些可选参数。 因此,这些可选参数通常用C#4.0中引入的命名争论功能表示。 要指定特定的可选参数,请使用参数名称后跟冒号。 例如:
public class AltLabel : Label
{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create("PointSize", // propertyName
typeof(double), // returnType
typeof(AltLabel), // declaringType
8.0, // defaultValue
propertyChanged: OnPointSizeChanged);
}
毫无疑问,propertyChanged是可选参数中最重要的参数,因为当属性发生更改时,类会使用此回调通知,直接来自对Set?Value的调用或通过CLR属性。
在此示例中,属性更改的处理程序称为OnPointSizeChanged。 只有在属性真正发生变化时才会调用它,而不是仅在它被设置为相同值时调用。 但是,因为OnPointSizeChanged是从静态字段引用的,所以方法本身也必须是静态的。 这是它的样子:
public class AltLabel : Label
{
static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
}
}
这看起来有点奇怪。 我们可能在程序中有多个AltLabel实例,但只要PointSize属性在这些实例中的任何一个中发生更改,就会调用相同的静态方法。 该方法如何确切地知道哪个AltLabel实例已更改?
该方法可以判断哪个实例的属性已更改,因为该实例始终是属性更改处理程序的第一个参数。 虽然第一个参数被定义为BindableObject,但在这种情况下,它实际上是AltLabel类型,并指示哪个AltLabel实例的属性已更改。 这意味着您可以安全地将第一个参数强制转换为AltLabel实例:
static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
AltLabel altLabel = (AltLabel)bindable;
}
然后,您可以在属性已更改的AltLabel的特定实例中引用任何内容。 对于此示例,第二个和第三个参数实际上是double类型,并指示先前的值和新值。
通常,这个静态方法通过将参数转换为实际类型来调用实例方法通常很方便:
public class AltLabel : Label
{
static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
((AltLabel)bindable).OnPointSizeChanged((double)oldValue, (double)newValue);
}
void OnPointSizeChanged(double oldValue, double newValue)
{
}
}
然后,实例方法可以像通常那样使用底层基类的任何实例属性或方法。
对于此类,此OnPointSizeChanged方法需要根据新的磅值和转换因子设置FontSize属性。 此外,构造函数需要根据默认的PointSize值初始化FontSize属性。 这是通过一个简单的SetLabelFontSize方法完成的。 这是最后的完整课程:
public class AltLabel : Label
{
public static readonly BindableProperty PointSizeProperty =
BindableProperty.Create("PointSize", // propertyName
typeof(double), // returnType
typeof(AltLabel), // declaringType
8.0, // defaultValue
propertyChanged: OnPointSizeChanged);
public AltLabel()
{
SetLabelFontSize((double)PointSizeProperty.DefaultValue);
}
public double PointSize
{
set { SetValue(PointSizeProperty, value); }
get { return (double)GetValue(PointSizeProperty); }
}
static void OnPointSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
((AltLabel)bindable).OnPointSizeChanged((double)oldValue, (double)newValue);
}
void OnPointSizeChanged(double oldValue, double newValue)
{
SetLabelFontSize(newValue);
}
void SetLabelFontSize(double pointSize)
{
FontSize = 160 * pointSize / 72;
}
}
实例OnPointSizeChanged属性也可以直接访问PointSize属性,而不是使用newValue。 在调用属性更改的处理程序时,基础属性值已经更改。 但是,您没有直接访问该基础值,就像私有字段支持CLR属性时一样。 该基础值是BindableObject的私有值,只能通过GetValue调用访问。
当然,没有什么能阻止使用AltLabel设置FontSize属性并覆盖PointSize设置的代码,但是我们希望这些代码能够意识到这一点。 这里有一些代码 - 一个名为PointSizedText的程序,它使用AltLabel显示4到12之间的点大小:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="PointSizedText.PointSizedTextPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="5, 20, 0, 0"
Android="5, 0, 0, 0"
WinPhone="5, 0, 0, 0" />
</ContentPage.Padding>
<StackLayout x:Name="stackLayout">
<toolkit:AltLabel Text="Text of 4 points" PointSize="4" />
<toolkit:AltLabel Text="Text of 5 points" PointSize="5" />
<toolkit:AltLabel Text="Text of 6 points" PointSize="6" />
<toolkit:AltLabel Text="Text of 7 points" PointSize="7" />
<toolkit:AltLabel Text="Text of 8 points" PointSize="8" />
<toolkit:AltLabel Text="Text of 9 points" PointSize="9" />
<toolkit:AltLabel Text="Text of 10 points" PointSize="10" />
<toolkit:AltLabel Text="Text of 11 points" PointSize="11" />
<toolkit:AltLabel Text="Text of 12 points" PointSize="12" />
</StackLayout>
</ContentPage>
以下是截图: