x:Name属性
在大多数实际应用程序中,代码隐藏文件需要引用XAML文件中定义的元素。 您在上一章的CodePlusXaml程序中看到了一种方法:如果代码隐藏文件知道XAML文件中定义的可视树的布局,它可以从根元素(页面本身)开始, 找到树中的特定元素。 此过程称为“在树上行走”,可用于定位页面上的特定元素。
通常,更好的方法是为XAML文件中的元素赋予类似于变量名的名称。 要执行此操作,请使用XAML固有的属性,称为Name。 因为前缀x几乎单独用于XAML固有的属性,所以此Name属性通常称为x:Name。
XamlClock项目演示了x:Name的使用。 这是包含两个Label控件的XamlClockPage.xaml文件,名为timeLabel和dateLabel:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlClock.XamlClockPage">
<StackLayout>
<Label x:Name="timeLabel"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="EndAndExpand" />
<Label x:Name="dateLabel"
HorizontalOptions="Center"
VerticalOptions="StartAndExpand" />
</StackLayout>
</ContentPage>
x:Name的规则与C#变量名称的规则相同。 (你很快就会明白为什么。)名称必须以字母或下划线开头,并且只能包含字母,下划线和数字。
与第5章中的时钟程序一样,XamlClock使用Device.StartTimer来触发定期事件以更新时间和日期。 这是XamlClockPage代码隐藏文件:
namespace XamlClock
{
public partial class XamlClockPage
{
public XamlClockPage()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(1), OnTimerTick);
}
bool OnTimerTick()
{
DateTime dt = DateTime.Now;
timeLabel.Text = dt.ToString("T");
dateLabel.Text = dt.ToString("D");
return true;
}
}
}
此计时器回调方法每秒调用一次。 该方法必须返回true才能继续计时器。 如果返回false,则计时器停止,必须通过另一次调用Device.Start?Timer重新启动。
回调方法引用timeLabel和dateLabel,就像它们是普通变量一样,并设置每个变量的Text属性:
这不是一个视觉上令人印象深刻的时钟,但它绝对是功能性的。
如何使用代码隐藏文件引用用x:Name标识的元素? 这很神奇吗? 当然不是。 当您检查XAML解析器在构建项目时从XAML文件生成的XamlClockPage.xaml.g.cs文件时,该机制非常明显:
namespace XamlClock {
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
public partial class XamlClockPage : global::Xamarin.Forms.ContentPage {
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
private global::Xamarin.Forms.Label timeLabel;
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
private global::Xamarin.Forms.Label dateLabel;
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
private void InitializeComponent() {
this.LoadFromXaml(typeof(XamlClockPage));
timeLabel = this.FindByName<global::Xamarin.Forms.Label>("timeLabel");
dateLabel = this.FindByName<global::Xamarin.Forms.Label>("dateLabel");
}
}
}
由于属性和完全限定类型,可能有点难以看到,但是当构建时XAML解析器咀嚼XAML文件时,每个x:Name属性都成为此生成的代码文件中的私有字段。这允许代码隐藏文件中的代码引用这些名称,就像它们是正常字段一样 - 它们肯定是。但是,字段最初为空。仅在运行时调用Initial?izeComponent时,才会通过FindByName方法设置两个字段,该方法在NameScopeExtensions类中定义。如果代码隐藏文件的构造函数尝试在InitializeComponent调用之前引用这两个字段,则它们将具有空值。
这个生成的代码文件还暗示了x的另一个规则:名称值现在非常明显但很少明确说明:名称不能复制代码后面文件中定义的字段或属性的名称。
因为这些是私有字段,所以只能从代码隐藏文件而不是其他类访问它们。如果ContentPage衍生产品需要将公共字段或属性公开给其他类,则必须自己定义它们。
显然,x:名称值在XAML页面中必须是唯一的。如果您在XAML文件中使用OnPlatform作为特定于平台的元素,这有时会成为问题。例如,这是一个XAML文件,它将OnPlatform的iOS,Android和WinPhone属性表示为属性元素,以选择三个Label视图之一:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PlatformSpecificLabels.PlatformSpecificLabelsPage">
<OnPlatform x:TypeArguments="View">
<OnPlatform.iOS>
<Label Text="This is an iOS device"
HorizontalOptions="Center"
VerticalOptions="Center" />
</OnPlatform.iOS>
<OnPlatform.Android>
<Label Text="This is an Android device"
HorizontalOptions="Center"
VerticalOptions="Center" />
</OnPlatform.Android>
<OnPlatform.WinPhone>
<Label Text="This is an Windows device"
HorizontalOptions="Center"
VerticalOptions="Center" />
</OnPlatform.WinPhone>
</OnPlatform>
</ContentPag```
OnPlatform的x:TypeArguments属性必须与目标属性的类型完全匹配。 此OnPlatform元素隐式设置为ContentPage的Content属性,此Content属性的类型为View,因此OnPlatform的x:TypeArguments属性必须指定View。 但是,OnPlatform的属性可以设置为从该类型派生的任何类。 设置为iOS,Android和WinPhone属性的对象实际上可以是不同的类型,只要它们都来自View。
尽管该XAML文件有效,但它并不完全是最佳的。 实例化并初始化所有三个Label视图,但只有一个设置为ContentPage的Content属性。 如果您需要从代码隐藏文件中引用Label并且为每个文件指定相同的名称,则会出现此方法的问题,如下所示:
以下XAML文件不起作用!
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PlatformSpecificLabels.PlatformSpecificLabelsPage">
<OnPlatform x:TypeArguments="View">
<OnPlatform.iOS>
<Label x:Name="deviceLabel"
Text="This is an iOS device"
HorizontalOptions="Center"
VerticalOptions="Center" />
</OnPlatform.iOS>
<OnPlatform.Android>
<Label x:Name="deviceLabel"
Text="This is an Android device"
HorizontalOptions="Center"
VerticalOptions="Center" />
</OnPlatform.Android>
<OnPlatform.WinPhone>
<Label x:Name="deviceLabel"
Text="This is a Windows device"
HorizontalOptions="Center"
VerticalOptions="Center" />
</OnPlatform.WinPhone>
</OnPlatform>
这不起作用,因为多个元素不能具有相同的名称。
您可以使用Device.OnPlatform为它们提供不同的名称并处理代码隐藏文件中的三个名称,但更好的解决方案是使特定于平台的标记保持尽可能小。 在此示例中,除Text外,所有Label属性都相同,因此只有Text属性需要特定于平台。 这是PlatformSpecificLabels程序的版本,它包含在本章的示例代码中。 它有一个Label,除了Text属性外,一切都是平台独立的:
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PlatformSpecificLabels.PlatformSpecificLabelsPage">
<Label x:Name="deviceLabel"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label.Text>
<OnPlatform x:TypeArguments="x:String"
iOS="This is an iOS device"
Android="This is an Android device"
WinPhone="This is a Windows device" />
</Label.Text>
</Label>
这是它的样子:
![201807052120000347](https://yqfile.alicdn.com/ad3d9d49c1d7d4a38c9580f9987b74de4dce71b4.jpeg)
Text属性是Label的content属性,因此您不需要上一个示例中的Label.Text标记。 这也有效:
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PlatformSpecificLabels.PlatformSpecificLabelsPage">
HorizontalOptions="Center"
VerticalOptions="Center">
<OnPlatform x:TypeArguments="x:String"
iOS="This is an iOS device"
Android="This is an Android device"
WinPhone="This is a Windows device" />
</Label>