第十一章:可绑定的基础结构(二)

查看BindableObject和BindableProperty

名为BindableObject和BindableProperty的类的存在最初可能有点令人困惑。请记住,BindableObject与Object非常相似,因为它充当了Xamarin.Forms API的一大块基类,特别是Element和VisualElement。
BindableObject为BindableProperty类型的对象提供支持。 BindableProperty对象扩展CLR属性。关于可绑定属性的最佳见解来自于您创建自己的一些内容 - 正如您将在本章结束之前所做的那样 - 但您也可以通过探索现有的可绑定属性来收集一些内容。
在第7章“XAML与代码”的开头,创建了两个具有许多相同属性设置的按钮,只是使用C#3.0对象初始化语法和另一个按钮在代码中设置了一个按钮的属性。在XAML中实例化并初始化。
这是一个名为PropertySettings的类似(但仅限代码)程序,它还以两种不同的方式创建和初始化两个按钮。第一个Label的属性以旧式方式设置,而第二个Label的属性使用更详细的技术设置:

public class PropertySettingsPage : ContentPage
{
    public PropertySettingsPage()
    {
        Label label1 = new Label();
        label1.Text = "Text with CLR properties";
        label1.IsVisible = true;
        label1.Opacity = 0.75;
        label1.HorizontalTextAlignment = TextAlignment.Center;
        label1.VerticalOptions = LayoutOptions.CenterAndExpand;
        label1.TextColor = Color.Blue;
        label1.BackgroundColor = Color.FromRgb(255, 128, 128);
        label1.FontSize = Device.GetNamedSize(NamedSize.Medium, new Label());
        label1.FontAttributes = FontAttributes.Bold | FontAttributes.Italic;
        Label label2 = new Label();
        label2.SetValue(Label.TextProperty, "Text with bindable properties");
        label2.SetValue(Label.IsVisibleProperty, true);
        label2.SetValue(Label.OpacityProperty, 0.75);
        label2.SetValue(Label.HorizontalTextAlignmentProperty, TextAlignment.Center);
        label2.SetValue(Label.VerticalOptionsProperty, LayoutOptions.CenterAndExpand);
        label2.SetValue(Label.TextColorProperty, Color.Blue);
        label2.SetValue(Label.BackgroundColorProperty, Color.FromRgb(255, 128, 128));
        label2.SetValue(Label.FontSizeProperty, 
                        Device.GetNamedSize(NamedSize.Medium, new Label()));
        label2.SetValue(Label.FontAttributesProperty, 
                        FontAttributes.Bold | FontAttributes.Italic);
        Content = new StackLayout
        {
            Children = 
            {
                label1,
                label2
            }
        };
    }
}

这两种设置属性的方法完全一致:
第十一章:可绑定的基础结构(二)
然而,替代语法似乎很奇怪。 例如:

label2.SetValue(Label.TextProperty, "Text with bindable properties");

什么是SetValue方法? SetValue由BindableObject定义,每个可视对象都派生自该对象。 BindableObject还定义了一个GetValue方法。
SetValue的第一个参数名称为Label.TextProperty,表示该名称
TextProperty是静态的,但尽管它的名字,它根本不是一个属性。 它是Label类的静态字段。 TextProperty也是只读的,它在Label类中的定义如下:

public static readonly BindableProperty TextProperty;

这是BindableProperty类型的对象。 当然,将一个字段命名为TextProperty似乎有点令人不安,但确实如此。 但是,因为它是静态的,所以它独立于任何可能存在或可能不存在的Label对象。
如果查看Label类的文档,您将看到它定义了10个属性,包括Text,TextColor,FontSize,FontAttributes等。 您还将看到10个具有名称TextProperty,TextCol?orProperty,FontSizeProperty,FontAttributesProperty等名称的BindableProperty类型的公共静态只读字段。
这些属性和字段密切相关。 实际上,在Label类的内部,Text CLR属性被定义为这样引用相应的TextProperty对象:

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

所以你明白为什么你的应用程序使用Label.TextProperty参数调用SetValue完全等同于直接设置Text属性,并且可能只是更快一点!
Label中Text属性的内部定义不是机密信息。 这是标准代码。虽然任何类都可以定义BindableProperty对象,但只有从Binda?bleObject派生的类才能调用实际实现类中属性的SetValue和GetValue方法。 GetValue方法需要进行强制转换,因为它被定义为返回对象。
维护Text属性所涉及的所有实际工作都在那些SetValue和GetValue调用中进行。 BindableObject和BindableProperty对象有效地扩展了标准CLR属性的功能,以提供系统化的方法:

  • 定义属性
  • 为属性提供默认值
  • 存储其当前值
  • 提供验证属性值的机制
  • 在单个班级中保持相关属性之间的一致性
  • 回应财产变化
  • 当属性即将更改并已更改时触发通知
  • 支持数据绑定
  • 支持样式
  • 支持动态资源

名为Text的属性与名为TextProp的BindableProperty的紧密关系反映在程序员谈论这些属性的方式中:有时程序员说Text属性由名为TextProperty的BindableProperty“支持”,因为TextProperty提供基础结构支持 文本。 但一个常见的捷径就是说Text本身就是一个“可绑定的属性”,通常没有人会被混淆。
并非每个Xamarin.Forms属性都是可绑定属性。 ContentPage的Content属性和Layout 的Children属性都不是可绑定属性。在VisualElement定义的28个属性中,26个由可绑定属性支持,但Bounds属性和Resources属性不支持。
与FormattedString结合使用的Span类不是从BindableOb?ject派生的。因此,Span不会继承SetValue和GetValue方法,也无法实现BindableProperty对象。
这意味着Label的Text属性由可绑定属性支持,但Span的Text prop不是。这有什么不同吗?
当然它有所作为!如果您回忆上一章中的DynamicVsStatic程序,您会发现DynamicResource处理Label的Text属性,但不处理Span的Text属性。是不是DynamicResource只能使用可绑定属性?
这个假设通过Element定义的以下公共方法的定义得到了很好的证实:

public void SetDynamicResource(BindableProperty property, string key);

当属性是DynamicResource标记扩展的目标时,这就是字典键与元素的特定属性相关联的方式。
此SetDynamicResource方法还允许您在代码中的属性上设置动态资源链接。这是来自DynamicVsStatic的仅代码版本的页面类,名为DynamicVsStaticCode。排除使用FormattedString和Span对象有点简化,但其他方面它非常准确地模仿了解析前一个XAML文件的方式,特别是如何通过XAML解析器设置Label元素的Text属性:

public class DynamicVsStaticCodePage : ContentPage
{
    public DynamicVsStaticCodePage()
    {
        Padding = new Thickness(5, 0);
        // Create resource dictionary and add item.
        Resources = new ResourceDictionary
        {
            { "currentDateTime", "Not actually a DateTime" }
        };
        Content = new StackLayout
        {
            Children = 
            {
                new Label
                {
                    Text = "StaticResource on Label.Text:",
                    VerticalOptions = LayoutOptions.EndAndExpand,
                    FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
                },
                new Label
                {
                    Text = (string)Resources["currentDateTime"],
                    VerticalOptions = LayoutOptions.StartAndExpand,
                    HorizontalTextAlignment = TextAlignment.Center,
                    FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
                },
                new Label
                {
                    Text = "DynamicResource on Label.Text:",
                    VerticalOptions = LayoutOptions.EndAndExpand,
                    FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
                }
            }
        };
        // Create the final label with the dynamic resource.
        Label label = new Label
        {
            VerticalOptions = LayoutOptions.StartAndExpand,
            HorizontalTextAlignment = TextAlignment.Center,
            FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
        };
        label.SetDynamicResource(Label.TextProperty, "currentDateTime");
        ((StackLayout)Content).Children.Add(label);
        // Start the timer going.
        Device.StartTimer(TimeSpan.FromSeconds(1),
            () =>
            {
                Resources["currentDateTime"] = DateTime.Now.ToString();
                return true;
            });
    }
}

第二个Label的Text属性直接从字典条目设置,并且在此上下文中使用字典似乎有点无意义。 但是,最后一个Label的Text属性通过调用SetDynamicResource绑定到字典键,这允许在字典内容更改时使属性更新:
第十一章:可绑定的基础结构(二)
考虑一下:如果它不能使用BindableProperty对象引用属性,那么这个SetDynamicResource方法的签名是什么?在方法调用中引用属性值很容易,但不是属性本身。有两种方法,例如System.Reflection命名空间中的PropertyInfo类或LINQ Expression对象。但是BindableProperty对象是专门为此目的而设计的,以及处理属性和字典键之间的底层链接的基本工作。
类似地,当我们在下一章中探索样式时,您将遇到一个用于与样式连接的Setter类。 Setter定义了一个名为Property的类型为BindableProperty的属性,该属性要求样式所针对的任何属性必须由可绑定属性支持。这允许在样式所针对的元素之前定义样式。
同样,对于数据绑定。 BindableObject类定义一个SetBinding方法,该方法与Element上定义的SetDynamicResource方法非常相似:

public void SetBinding(BindableProperty targetProperty, BindingBase binding);

再次注意第一个参数的类型。 数据绑定所针对的任何属性都必须由可绑定属性支持。
出于这些原因,无论何时创建自定义视图并需要定义公共属性,您的默认倾向应该是将它们定义为可绑定属性。 只有在经过仔细考虑后,您才会得出结论:如果您撤消并定义普通的CLR属性,则该属性不是必需或适合于由样式或数据绑定作为目标。
所以每当你创建一个派生自BindableObject的类时,你应该在该类中输入的第一段代码之一就是“public static readonly BindableProperty” - 也许是所有Xamarin.Forms编程中最具特色的四个单词序列。

上一篇:IMP-00013: 只有 DBA 才能导入由其他 DBA 导出的文件


下一篇:让你的Blend“编辑其他模板”菜单里出现你的Style