且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-06-26 18:42:00

只读可绑定属性
假设您正在使用一个应用程序,在该应用程序中,可以方便地知道Label元素显示的文本中的单词数。 也许您希望将该工具直接构建到源自Label的类中。 我们称这个新类为CountedLabel。
到目前为止,您首先想到的是定义一个名为WordCount?属性的BindableProperty对象和一个名为WordCount的相应CLR属性。
但是等一下:只有从CountedLabel类中设置这个WordCount属性才有意义。 这意味着WordCount CLR属性不应具有公共集访问器。 它应该这样定义:

public int WordCount
{
    private set { SetValue(WordCountProperty, value); }
    get { return (double)GetValue(WordCountProperty); }
}

get访问器仍然是公共的,但set访问器是私有的。 那够了吗?
不完全是。 尽管CLR属性中有私有set访问器,但CountedLabel外部的代码仍然可以使用CountedLabel.WordCountProperty可绑定属性对象调用SetValue。 这种类型的财产设置也应该被禁止。 但是,如果WordCountProp?erty对象是公共的,那怎么能工作呢?
解决方案是使用BindableProperty.Create?ReadOnly方法创建只读的可绑定属性。 Xamarin.Forms API本身定义了几个只读的可绑定属性 - 例如,Width和
由VisualElement定义的高度属性。
以下是如何制作自己的:
第一步是使用与BindableProperty.Create相同的参数调用BindableProperty.CreateReadOnly。 但是,CreateReadOnly方法返回Binda?blePropertyKey的对象而不是BindableProperty。 将此对象定义为static和readonly,与BindableProperty一样,但将其设置为类的私有:


public class CountedLabel : Label
{
    static readonly BindablePropertyKey WordCountKey =
        BindableProperty.CreateReadOnly("WordCount", //propertyName
                                        typeof(int), // returnType
                                        typeof(CountedLabel), // declaringType
                                        0); // defaultValue
 
}

不要将此BindablePropertyKey对象视为加密密钥或类似的东西。 它更简单 - 实际上只是一个私有的对象。
第二步是使用BindablePropertyKey的BindableProperty属性创建一个公共BindableProperty对象:

public class CountedLabel : Label
{
 
    public static readonly BindableProperty WordCountProperty = WordCountKey.BindableProperty;

}

这个BindableProperty对象是公共的,但它是一种特殊的BindableProperty:它不能用于SetValue调用。 尝试这样做会引发InvalidOperationException。
但是,SetValue方法有一个重载,它接受一个BindablePropertyKey对象。 CLR set访问器可以使用此对象调用SetValue,但是此set访问器必须是私有的,以防止在类外部设置属性:

public class CountedLabel : Label
{
 
    public int WordCount
    {
        private set { SetValue(WordCountKey, value); }
        get { return (int)GetValue(WordCountProperty); }
    }
 
}

现在可以在CountedLabel类中设置WordCount属性。 但课程什么时候应该设定呢? 此CountedLabel类派生自Label,但它需要检测Text prop?erty何时更改,以便它可以对单词进行计数。
Label有TextChanged事件吗? 不,不是的。 但是,BindableObject实现了INotifyPropertyChanged接口。 这是一个非常重要的.NET接口,特别是对于实现Model-View-ViewModel(MVVM)体系结构的应用程序。 在第18章中,您将了解如何在自己的数据类中使用它。
INotifyPropertyChanged接口在System.ComponentModel命名空间中定义,如下所示:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

每当由BindableProperty支持的任何属性发生更改时,从BindableObject派生的每个类都会自动触发此PropertyChanged事件。 此事件附带的PropertyChangedEventArgs对象包含一个名为PropertyName的属性,其属性为string,用于标识已更改的属性。
因此,所有必要的是,CountedLabel为PropertyChanged事件附加处理程序并检查属性名称“Text”。 从那里它可以使用它想要的任何技术来计算字数。 完整的CountedLabel类在PropertyChanged事件上使用lambda函数。 处理程序调用Split将字符串分解为单词,并查看有多少部分结果。 Split方法基于空格,短划线和短划线(Unicode u2014)拆分文本:

public class CountedLabel : Label
{
    static readonly BindablePropertyKey WordCountKey =
        BindableProperty.CreateReadOnly("WordCount", // propertyName
                                        typeof(int), // returnType
                                        typeof(CountedLabel), // declaringType
                                        0); // defaultValue
    public static readonly BindableProperty WordCountProperty = WordCountKey.BindableProperty;
    public CountedLabel()
    {
        // Set the WordCount property when the Text property changes.
        PropertyChanged += (object sender, PropertyChangedEventArgs args) =>
        {
            if (args.PropertyName == "Text")
            {
                if (String.IsNullOrEmpty(Text))
                {
                    WordCount = 0;
                }
                else
                {
                    WordCount = Text.Split(' ', '-', '\u2014').Length;
                }
            }
        };
    }
    public int WordCount
    {
        private set { SetValue(WordCountKey, value); }
        get { return (int)GetValue(WordCountProperty); }
    }
}

该类包含System.ComponentModel命名空间的using指令,用于处理程序的Property?ChangedEventArgs参数。 注意:Xamarin.Forms定义了一个名为Prop?ertyChangingEventArgs(现在时)的类。 这不是你想要的PropertyChanged han?dler。 你想要PropertyChangedEventArgs(过去时)。
因为Split方法的这个调用将文本分成空白字符,破折号和破折号,所以您可能会认为将使用包含一些破折号和破折号的文本演示CountedLabel。 这是真的。 BaskervillesCount程序是第3章中Baskervilles程序的变体,但是这里的文本段落用CountedLabel显示,并且包含常规Label以显示单词count:

<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="BaskervillesCount.BaskervillesCountPage"
            Padding="5, 0">
    <StackLayout>
        <toolkit:CountedLabel x:Name="countedLabel"
                              VerticalOptions="CenterAndExpand"
                              Text=
"Mr. Sherlock Holmes, who was usually very late in
the mornings, save upon those not infrequent
occasions when he was up all night, was seated at
the breakfast table. I stood upon the hearth-rug
and picked up the stick which our visitor had left
behind him the night before. It was a fine, thick
piece of wood, bulbous-headed, of the sort which
is known as a &#x201C;Penang lawyer.&#x201D; Just
under the head was a broad silver band, nearly an
inch across, &#x201C;To James Mortimer, M.R.C.S.,
from his friends of the C.C.H.,&#x201D; was engraved
upon it, with the date &#x201C;1884.&#x201D; It was
just such a stick as the old-fashioned family
practitioner used to carry&#x2014;dignified, solid,
and reassuring." />
        <Label x:Name="wordCountLabel"
               Text="???"
               FontSize="Large"
               VerticalOptions="CenterAndExpand"
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage> 

常规Label在代码隐藏文件中设置:

public partial class BaskervillesCountPage : ContentPage
{
    public BaskervillesCountPage()
    {
        InitializeComponent();
        int wordCount = countedLabel.WordCount;
        wordCountLabel.Text = wordCount + " words";
    }
}

它计算的单词计数是基于这样的假设:文本中的所有连字符分开两个单词,并且“hearth-rug”和“bulbous-headed”应该被计为每个单词两个单词。 当然,这并不总是正确的,但字数不像算法那么简单,因为这个代码可能意味着:
第十一章:可绑定的基础结构(四)
如果文本在程序运行时动态更改,程序将如何构建? 在这种情况下,每当CountedLabel对象的WordCount属性发生更改时,都需要更新单词计数。 您可以在Count?edLabel对象上附加PropertyChanged处理程序,并检查名为“WordCount”的属性。
但是,如果您尝试从XAML设置此类事件处理程序,请务必谨慎 - 例如,如下所示:

<toolkit:CountedLabel x:Name="countedLabel"
         VerticalOptions="CenterAndExpand"
         PropertyChanged="OnCountedLabelPropertyChanged"
         Text=" __ " />

您可能希望在代码隐藏文件中编码事件处理程序,如下所示:

void OnCountedLabelPropertyChanged(object sender, 
                                   PropertyChangedEventArgs args)
{
    wordCountLabel.Text = countedLabel.WordCount + " words";
}

当XAML解析器设置Text属性时,该处理程序将触发,但事件处理程序正在尝试设置尚未实例化的第二个Label的Text属性,这意味着wordCountLabel字段仍设置为null。 这是第15章在使用交互式控件时会再次出现的问题,但在第16章中处理数据绑定时,它将会得到很好的解决。
在AbsoluteLay的第14章中还有另一种可绑定属性的变体吗?out:这是附加的可绑定属性,它在实现某些类型的布局时非常有用,你将在第26章中发现, “自定义布局。”
同时,让我们看一下可绑定属性最重要的应用之一:样式。