只读可绑定属性
假设您正在使用一个应用程序,在该应用程序中,可以方便地知道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 “Penang lawyer.” Just
under the head was a broad silver band, nearly an
inch across, “To James Mortimer, M.R.C.S.,
from his friends of the C.C.H.,” was engraved
upon it, with the date “1884.” It was
just such a stick as the old-fashioned family
practitioner used to carry—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章中发现, “自定义布局。”
同时,让我们看一下可绑定属性最重要的应用之一:样式。