XAML基础知识
XAML(eXtensible Application Markup Language)可扩展应用程序标记语言,允许开发者在Xamarin.Forms应用中采用标记而不是代码来定义用户界面。XAML在Xamarin.Forms 程序中不是必须的,但通常它比后台等效代码会更简洁和更直观,并可能会非常有用。 XAML 特别适用于常用 MVVM (Model-View-ViewModel)应用程序体系结构:XAML定义了通过基于XAML的数据绑定链接到ViewModel代码的视图。
XAML是Microsoft创建的一种基于XML的语言,是用于实例化和初始化对象以及在父子层次结构中组织这些对象的编程代码的替代方法。
常用于微软的技术:WPF、Silverlight、UWP。
XAML也是Xamarin.Forms的一部分,Xamarin.Forms是适用于iOS,Android和UWP移动设备的跨平台基于本机的编程接口。 在XAML文件中,Xamarin.Forms开发人员可以使用所有Xamarin.Forms视图,布局和页面以及自定义类来定义用户界面。. XAML文件可以被编译,也可以嵌入到可执行文件中,无论哪种方法,XAML信息在构建时被解析以定位已命名的对象,然后在运行时再次被解析以实例化和初始化对象,并在这些对象和编程代码之间建立链接。
XAML 具有几大优势(与等效的后台代码相比):
- XAML 通常会更简洁和可读。
- XML中固有的父-子层次结构允许XAML以更清晰的视觉效果模仿用户界面对象的父-子层次结构。
- XAML可以很容易地由程序员手工编写,但也可以通过可视化设计工具生成和使用XAML。
当然,也有缺点,主要与标记语言固有的限制有关:
- XAML 不能包含代码。 必须在代码文件中定义所有事件处理程序。
- XAML 不能包含重复处理的循环。 (但是,多个 Xamarin.Forms 视觉对象 — 最值得注意的是 ListView— 可以生成多个子级中的对象基于其
ItemsSource
集合。) - XAML 不能包含条件处理 (但是,数据绑定可以引用,可有效地处理某些条件的代码基于绑定转换器。)
- XAML通常不能实例化不定义无参数构造函数的类。(然而,有时有一种方法可以绕过这个限制
- XAML 通常不能调用方法。 (同样,此限制有时可以克服。)
在 Xamarin.Forms 应用程序中还没有可视化设计器生成 XAML。 所有 XAML 都必须手动编写,但是有XAML 预览程序。刚接触XAML的程序员可能想要频繁地构建和运行他们的应用程序,特别是在任何明显不正确的事情之后。即使是在XAML中有丰富经验的开发人员也知道实验是有回报的。
XAML is basically XML, but XAML has some unique syntax features. The most important are:
- Property elements
- Attached properties
- Markup extensions
这些特性不是XML扩展。XAML完全是合法的XML。但是这些XAML语法特性以独特的方式使用XML。下面的文章将详细讨论它们,最后介绍如何使用XAML实现MVVM。
1、XAML入门
XAML用于定义页面的可视化内容,并和C#后台代码文件一起工作。
后台代码为标记提供代码支持, 这两个文件一起定义包含子视图和属性初始化的类。 在 XAML 文件中,类和属性由XML元素和特性引用,并建立标记和代码之间的链接。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.MainPage">
<StackLayout>
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin Forms!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
View Code
XAML文件剖析
1、标记
XAML是一种声明式语言,一个标签声明一个元素(每个元素对应内存中的一个对象,对象间的层级关系要么并列要么包含)。标签特性(Attribute)语法层面,而属性(property)是对象层面,特性和属性并不是完全对应,特性一般比属性多。
以下是ContentPage的Attribute:
xmlns是在声明或引用命名空间,一般的规则是公司网站的名字+自定义的名字。声明命名空间的好处:当类重名时,可以用命名空间区分。
xmlns:[可选的映射前缀] = “命名空间”
解释:
- 映射前缀可以无,也可以有(名字任意)
- 如果没有写可选映射前缀,则意味着所有来自这个命名空间的标签都不用加前缀,这个也是默认命名空间(只能只有一个),
XAML中引入命名空间:先要添加dll,如在根元素写上:xmlns:c=”clr-namespace:命名空间;assembly=程序集的名字” ,后续使用时,就可以<c:Button>...
上述:
第一个XML名称空间声明:没有映射前缀,引用Xamarin.Forms名称空间,说明在XAML文件中引用Xamarin.Forms中的类的标记是不用前缀,例如ContentPage 。
【注:url地址形式的名称空间其实是XAML解析器的一个硬性编码,只要见到这些固定的字符串,就会把一系列必要的程序集和程序集中包含的.NET命名空间引用进来】
第二个名称空间声明定义了一个x前缀。它用于XAML本身固有的、由XAML的其他实现支持的几个元素和属性。然而,这些元素和属性根据URI中嵌入的年份略有不同。Xamarin.Forms支持2009年XAML规范,但不是全部。
常用的X:Name="xxx" ,然后在cs代码中就可以直接使用此控件。
不难看出:第一个是与绘制UI相关的程序集,是表示层面上的东西;第二个是对应XAML语言解析处理相关的程序集,是语言层面上的东西。
第三个 Local命名空间声明允许您访问.NET标准库项目中的其他类。用来让我们在xaml中引用其他程序集中的类,类似于Using的作用.
最后一个,x前缀用于一个名为Class的属性,x前缀定义的这个命名空间是用于XAML解析功能的。x:Class 解析成C#类(分部类)。
Class属性指定一个完全限定的. net类名:XamlSamples命名空间中的MainPage类。这意味着这个XAML文件在XamlSamples命名空间中定义了一个名为MainPage的新类,这个类派生自ContentPage标记,其中显示了x: class属性。
2、后台代码
using Xamarin.Forms;
namespace XamlSamples
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
}
View Code
MainPage类派生自ContentPage,但请注意部分类定义。这意味着MainPage应该有另一个局部类定义,但是它在哪里?那个初始化的ecomponent方法是什么
当 Visual Studio 生成项目时,它会分析要生成的 XAML 文件C#代码文件。 如果查看XamlSamples\XamlSamples\obj\Debug目录中,您会发现名为的文件XamlSamples.MainPage.xaml.g.cs。 G 表示生成的,这是MainPage的分部类其他定义
,其中包含从MainPage构造函数调用的InitializeComponent方法的定义。 这两部分会编译在一起。 根据是否编译了XAML, XAML文件或XAML文件的二进制形式都嵌入到可执行文件中。
在运行时,在特定平台项目的代码调用LoadApplication
方法,并向它传递.NET Standard 库中的新实例App
类,App
类构造函数实例化MainPage(
该类的构造函数调用InitializeComponent)
,InitializeComponent调用LoadFromXaml方法
从.NET Standard 库中提取 XAML 文件 (或其编译的二进制文件)。LoadFromXaml
初始化 XAML 文件中定义的所有对象、 以父-子关系将它们连接在一起,将代码中定义的事件处理程序附加到XAML文件中的事件集,并将生成的对象树设置为页面的内容。
3、设置页面内容
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.HelloXamlPage"
Title="Hello XAML Page">
<ContentPage.Content>
<Label Text="Hello, XAML!"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
Rotation="-15"
IsVisible="true"
FontSize="Large"
FontAttributes="Bold"
TextColor="Blue" />
</ContentPage.Content>
</ContentPage>
View Code
ContentPage.Content
标记称为属性元素标记。 Content
是ContentPage的一个属性
,并通常设置为单个视图或带有子视图的布局。 通常情况下属性变为 XAML 中的特性,但是很难将内容属性设置为复杂对象。 因此,属性被表示为一个XML元素,由类名和用句点分隔的属性名组成。 现在可以在ContentPage之间设置Content属性标签。
此时,类、属性和XML之间的关系应该很明显:Xamarin.Forms类(如ContentPage或Label)作为XML元素出现在XAML文件中。类的属性(包括ContentPage上的Title)和Label的七个属性通常作为XML属性出现。
扩展
上述ContentPage.Content
标记 仅包含单个Label
的页上,但这种情况很少。大多数ContentPage派生将Content属性设置为某种布局(不需显式ContentPage.Content
标记),例如StackLayout。StackLayout的子属性被定义为类型为IList<View>,但它实际上是类型为ElementCollection<View>的对象,该集合可以用多个视图或其他布局填充。在XAML中,这些父-子关系是用普通的XML层次结构建立的。下面是一个名为XamlPlusCodePage的新页面的XAML文件
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.XamlPlusCodePage"
Title="XAML + Code Page">
<StackLayout>
<Slider VerticalOptions="CenterAndExpand" />
<Label Text="A simple Label"
Font="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="Click Me!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
View Code
4、生成文件剖析
当xaml中定义了x:Name="" 之后,XamlSamples.MainPage.xaml.g.cs文件中会生成私有字段,如下:
该字段的声明允许 在您管辖范围内的分部类文件的任何地方自由使用。在运行时,在解析XAML之后分配字段。这意味着,当分部类 构造函数开始时valueLabel字段为空,但在调用InitializeComponent后它就是可用的。
InitializeComponent将控件返回给构造函数后,页面的视觉效果就像在代码中实例化和初始化一样得到了构造。XAML文件不再在类中扮演任何角色。您可以以任何您想要的方式操作页面上的这些对象,例如,通过向StackLayout添加视图,或者将页面的内容属性完全设置为其他内容。您可以通过检查页面的内容属性和布局子集合中的项来遍历树。您可以通过这种方式设置视图的属性等等。
自由地,它是您的页面,而XAML只是一个构建其内容的工具。
2、基本XAML语法
XAML主要用于实例化和初始化对象。对象属性赋值的方法:使用字符串进行简单的复制;复杂对象赋值需要属性元素和附加属性的基本XAML语法特性。
1、Property Elements 属性元素
类的属性(property)一般设置为xml的特性(attribute):
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large"
TextColor="Aqua" />
View Code
但是可选用属性元素来代替它,以TextColor为例:
<Label Text="Hello, XAML!"
VerticalOptions="Center"
FontAttributes="Bold"
FontSize="Large">
<Label.TextColor>
Aqua
</Label.TextColor>
</Label>
View Code
这两种指定TextColor属性的方法在功能上是等效的,但不要对同一属性使用这两种方法,因为这实际上将属性设置两次,并且可能会模棱两可。
Label
is an object element. It is a Xamarin.Forms object expressed as an XML element.Text
,VerticalOptions
,FontAttributes
andFontSize
are property attributes. They are Xamarin.Forms properties expressed as XML attributes.- In that final snippet,
TextColor
has become a property element. It is a Xamarin.Forms property but it is now an XML element.
对于XML解码器,Label.TextColor只是一个普通的子元素。
然而,在XAML中,这种语法非常特殊。属性元素的规则之一是 Label.TextColor标签中不能出现任何其他内容,属性元素开始和结束标记之间的内容总是定义为属性的Value。
定义多个这样的属性元素,写法上看起来好像更复杂了。但是有时还是很有用的:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
Title="Grid Demo Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
...
</Grid>
</ContentPage>
View Code
网格有两个属性,分别称为rowdefinition和columndefinition。这两个属性属于RowDefinitionCollection和ColumnDefinitionCollection类型,它们是RowDefinition和ColumnDefinition对象的集合。您需要使用属性元素语法来设置这些集合。
2、Attached Properties 附加属性(可绑定的属性)
上面定义了Grid的行和列,程序员还必须有某种方法来指示网格的每个子元素所在的行和列。
Grid的子元素标签 使用以下属性来指定其在哪个格子(行列):Grid.Row、Grid.Column,默认为0
跨多行多列的属性:Grid.RowSpan、
Grid.ColumnSpan,默认为1
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.GridDemoPage"
Title="Grid Demo Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Label Text="Autosized cell"
Grid.Row="0" Grid.Column="0"
TextColor="White"
BackgroundColor="Blue" />
<BoxView Color="Silver"
HeightRequest="0"
Grid.Row="0" Grid.Column="1" />
<BoxView Color="Teal"
Grid.Row="1" Grid.Column="0" />
<Label Text="Leftover space"
Grid.Row="1" Grid.Column="1"
TextColor="Purple"
BackgroundColor="Aqua"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
<Label Text="Span two rows (or more if you want)"
Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"
TextColor="Yellow"
BackgroundColor="Blue"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
<Label Text="Span two columns"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
TextColor="Blue"
BackgroundColor="Yellow"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
<Label Text="Fixed 100x100"
Grid.Row="2" Grid.Column="2"
TextColor="Aqua"
BackgroundColor="Red"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
View Code
仅从语法判断,这些Grid.Row
, Grid.Column
, Grid.RowSpan
,和Grid.ColumnSpan
似乎是Grid的静态字段或属性,但有趣的是,Grid
不定义任何名为Row
, Column
, RowSpan
,或ColumnSpan的属性
。
相反,Grid定义了四个可绑定属性,分别是RowProperty、ColumnProperty、RowSpanProperty和ColumnSpanProperty。这些是可绑定属性的特殊类型,称为附加属性。它们由Grid类定义,但设置在网格的子级上。
当您希望在代码中使用这些附加属性时,Grid类提供了名为SetRow、GetColumn等的静态方法。但在XAML中,这些附加属性使用简单的属性名称设置为网格子元素中的属性。
为什么叫附加属性?
附加属性在XAML文件中始终可识别为属性,其中包含用句点分隔的类名和属性名。
它们被称为附加属性,因为它们由一个类(在本例中是Grid)定义,但附加到其他对象(在本例中是Grid的子对象)。在布局期间,网格可以查询这些附加属性的值,以知道将每个子元素放置在何处。
3、Content Properties 内容属性
上面也提到,ContentPage标记之间一般用某种布局,而不是content属性。
查看ContentPage
类的定义:
[Xamarin.Forms.ContentProperty("Content")]
public class ContentPage : TemplatedPage
这意味着内容属性元素标记不是必需的,出现在ContentPage标记开始和结束之间的任何XML内容都假定被分配给content属性。
4、使用 OnPlatform 平台差异
在单页应用程序中,通常在页面上设置Padding属性,以避免覆盖iOS状态栏。 在代码中,可以为此使用Device.RuntimePlatform属性:
if (Device.RuntimePlatform == Device.iOS)
{
Padding = new Thickness(0, 20, 0, 0);
}
在XAML中实现:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="...">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
<On Platform="Android" Value="0, 0, 0, 0" />
<On Platform="UWP" Value="0, 0, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
...
</ContentPage>
需要使用Xamarin.Forms.OnPlatform
and On
classes,
1、OnPlatform
是一个泛型类,需要指定泛型类型参数,这里,Thinckness是Padding属性的类型。
幸运的是,有一个XAML属性专门用于定义泛型参数,称为x:TypeArguments, 这应该与您要设置的属性的类型匹配。
OnPlatform
有一个IList<On> Platforms对象。 该属性使用属性元素标记,<OnPlatform.Platforms>
2、添加On
元素。 为每个On对象 设置Platform
属性和Value
属性,应用于Thickness
属性
OnPlatform的内容属性
是Platforms
,因此可以删除属性元素标记(<OnPlatform.Platforms>)
Platform
的属性On
属于类型IList<string>
,因此,如果值是相同的则可以包含多个平台:<On Platform="Android, UWP" Value="0, 0, 0, 0" />
3、XAML标记扩展
XAML标记扩展是XAML中的重要功能,它允许将属性设置为从其他源间接引用的对象或值。 XAML标记扩展对于共享对象和引用整个应用程序中使用的常量特别重要,但是它们在数据绑定中找到了最大的用途。
1、概念
通常情况下,在xaml中为对象的属性设置显式值,例如字符串、数字、枚举值或者在后台将值转为的字符串。但是有时,属性必须改为引用在其他位置定义的值,或者可能需要在运行时通过代码进行一些处理,为此,可以使用XAML标记扩展。
XAML标记扩展不是XML的扩展,XAML是完全合法的XML。 之所以称为“扩展”,是因为它们由实现IMarkupExtension的类中的代码支持,可以编写自己的自定义标记扩展。
在许多情况下,XAML标记扩展名在XAML文件中可立即识别,因为它们以大括号{ }分隔的属性设置出现,但有时标记扩展名作为常规元素出现在标记中。
大多数赋值都是为属性生成一个新对象,但有时需要把同一个对象赋值给两个对象的属性,可以考虑使用标记扩展。
所谓标记扩展,实际上是一种特殊的Attribute=value语法,特殊的地方在与Value字符串由一对花括号及其括起来的内容组成,XAML编译器会对花括号中的内容作出解析,生成相应的对象。
eg:标记扩展 Text="{Binging ElementName=silder1,path=Value,Mode=OneWay}"
使用Binging类的实例将TextBox的Text属性 依赖在silder的Value上。
这个语法类似C#3.0中的对象初始化语法。
2、注意
标记扩展是可以嵌套的,eg Text="{Binging Source={staticResource myDdata},Path=PersonName}"
共享的资源
某些XAML页面包含多个视图,这些视图的属性设置为相同的值。一种受欢迎的解决方案是将此类值或对象存储为资源字典( resource dictionary)。
VisualElement类定义了一个名为ResourceDictionary类型的Resources的属性,该属性是具有字符串类型的键和类型object的值的字典。 您可以将对象放入此字典中,然后从标记中引用它们,所有这些都在XAML中。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">3</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>
<x:Double x:Key="fontSize">24</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
<Button Text="Do that!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
<Button Text="Do the other thing!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
</StackLayout>
</ContentPage>
View Code
使用:执行标记扩展StaticResource(其内容属性是Key),即"{StaticResource horzOptions}" 等价"{StaticResource Key=horzOptions}"
X:static 标记扩展
Static
vs StaticResource,
StaticResource
从资源字典中返回对象,而x:Static
访问以下项之一:
- 公共静态字段
- 公共静态属性
- 公共常量字段
- 枚举成员。
定义资源字典的XAML实现 支持StaticResource标记扩展,然而x:Static是XAML的固有部分,如x前缀所示。
x:Static
标记扩展还可以从你自己的代码中引用静态字段或属性,主要配置xmlns:local,自己定义的类的命名空间。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="XamlSamples.StaticConstantsPage"
Title="Static Constants Page"
Padding="{x:Static local:AppConstants.PagePadding}">
<StackLayout>
<Label Text="Hello, XAML!"
TextColor="{x:Static local:AppConstants.BackgroundColor}"
BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
Font="{x:Static local:AppConstants.TitleFont}"
HorizontalOptions="Center" />
<BoxView WidthRequest="{x:Static sys:Math.PI}"
HeightRequest="{x:Static sys:Math.E}"
Color="{x:Static local:AppConstants.ForegroundColor}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="100" />
</StackLayout>
</ContentPage>
View Code
其他标准标记扩展
一些标记扩展是XAML固有的,并且在Xamarin.Forms XAML文件中受支持。 其中一些并不经常使用,但在您需要时必不可少:
- 如果属性具有非
null
值的默认值,但你想要将其设置为null
,将其设置为{x:Null}
标记扩展。 - 如果某个属性属于类型
Type
,你可以将其分配给Type
对象使用标记扩展{x:Type someClass}
。在<Style TargetType="{x:Type Label}">中用的多 - 可以使用x:Array标记扩展在XAML中定义数组,此标记扩展名具有名为Type的必需属性,该属性指示数组中元素的类型。
4、数据绑定基础知识
数据绑定允许链接两个对象的属性,以便一个对象的更改导致另一个对象的更改。 这是一个非常有价值的工具,虽然可以完全在代码中定义数据绑定,但是XAML提供了快捷方式和便利性。 因此,Xamarin.Forms中最重要的标记扩展之一是Binding。
数据绑定
数据绑定连接两个对象的属性,称为源和目标。 在cs代码中,需要两个步骤:必须将目标对象的BindingContext属性设置为源对象,并且必须在目标对象上调用SetBinding方法(通常与Binding类结合使用)以绑定该对象的属性,该对象指向源对象的属性。
目标属性必须是可绑定的属性,这意味着目标对象必须派生自BindableObject。 在线Xamarin.Forms文档指出了哪些属性是可绑定属性。 Label的Text属性与可绑定属性TextProperty关联
在xaml中中,也同样是两个步骤,只不过用Binding
标记扩展代替SetBinding
调用和Binding
类。
在XAML中定义数据绑定时,有多种方法可以设置目标对象的BindingContext。 有时,它是从代码隐藏文件中设置的,有时是使用StaticResource或x:Static标记扩展名,有时是BindingContext属性元素标记的内容
View-View绑定
可以定义要链接的同一页面上的两个视图(控件)的属性的数据绑定。 在这种情况下,设置BindingContext
的目标对象使用x:Reference
标记扩展。
<ContentPage.Content>
<StackLayout>
<Label x:Name="label"
Text="TEXT"
FontSize="40"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" >
<!--有多种写法,参考官网-->
<Label.Scale>
<Binding Source="{x:Reference slider}"
Path="Value"/>
</Label.Scale>
</Label>
<Slider x:Name="slider"
Minimum="-2"
Maximum="2"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
View Code
ReferenceExtension的内容属性是Name,因此Name= 可以省略。
绑定和集合
没有什么比模板ListView更好地说明XAML和数据绑定的功能了。
ListView定义了IEnumerable类型的ItemsSource属性,并显示该集合中的items。
但是,可以通过使用模板以所需的任何方式显示ListView集合中的项目,该模板涉及从Cell派生的类。 将为ListView中的每个项目克隆模板,并将在模板上设置的数据绑定转移到各个克隆。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ListView ItemsSource="{x:Static local:NamedColor.All}" />
</ContentPage>
View Code
ListView在处理可能在基础数据中动态发生的更改方面非常复杂,但前提是您采取了某些步骤。 如果分配给ListView的ItemsSource属性的项目集合在运行时发生更改(即,可以将项目添加到集合中或从集合中删除),请对这些项目使用ObservableCollection类。 ObservableCollection实现INotifyCollectionChanged接口,并且ListView将为CollectionChanged事件安装一个处理程序。
5、从数据绑定到 MVVM
XAML发明了Model-View-ViewModel(MVVM)体系结构模式,该模式在三个软件层之间进行了分隔-XAML用户界面,称为View; 基础数据,称为模型; 在视图和模型之间的中介称为ViewModel。 View和ViewModel通常通过XAML文件中定义的数据绑定连接。 View的BindingContext通常是ViewModel的实例。
简单的ViewModel
先来看一下不使用Viewmodel的程序:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="XamlSamples.OneShotDateTimePage"
Title="One-Shot DateTime Page">
<StackLayout BindingContext="{x:Static sys:DateTime.Now}"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label Text="{Binding Year, StringFormat='The year is {0}'}" />
<Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
<Label Text="{Binding Day, StringFormat='The day is {0}'}" />
<Label Text="{Binding StringFormat='The time is {0:T}'}" />
</StackLayout>
</ContentPage>
View Code
BindingContext
是一个非常特殊的属性:在元素BindingContext
上设置时,该元素的所有子级都将继承该元素。 这意味着StackLayout的所有子级具有此相同BindingContext
,并且它们可以包含简单绑定到该对象的属性。
上例,有两个子级似乎缺少绑定路径的绑定,这意味着DateTime.Now
本身的值用于StringFormat。
考虑MVVM时,Model和ViewModel是完全用代码编写的类。 视图通常是一个XAML文件,它通过数据绑定引用ViewModel中定义的属性。
ViewModel通常实现INotifyPropertyChanged接口,这意味着该类的任何一个属性更改时都会触发PropertyChanged事件。 Xamarin.Forms中的数据绑定机制将处理程序附加到此PropertyChanged事件,以便可以在属性更改时得到通知,并使用新值更新目标。
xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ClockPage"
Title="Clock Page">
<Label Text="{Binding DateTime, StringFormat='{0:T}'}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label.BindingContext>
<local:ClockViewModel />
</Label.BindingContext>
</Label>
</ContentPage>
View Code
viewModel:
using System;
using System.ComponentModel;
using Xamarin.Forms;
namespace XamlSamples
{
class ClockViewModel : INotifyPropertyChanged
{
DateTime dateTime;
public event PropertyChangedEventHandler PropertyChanged;
public ClockViewModel()
{
this.DateTime = DateTime.Now;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
this.DateTime = DateTime.Now;
return true;
});
}
public DateTime DateTime
{
set
{
if (dateTime != value)
{
dateTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
}
}
}
get
{
return dateTime;
}
}
}
}
View Code
使用ViewModels命令
在许多情况下,MVVM模式仅限于数据项的操作:ViewModel中的数据对象与View中的用户界面对象并行。
但是,有时View需要包含触发ViewModel中各种动作的按钮。 但是ViewModel不能包含按钮的Clicked处理程序,因为这会将ViewModel绑定到特定的用户界面范例。
为了使ViewModels更加独立于特定的用户界面对象,但仍允许在ViewModel中调用方法,可以用一个命令接口。 Xamarin.Forms中的以下元素支持此命令接口:
Button
MenuItem
ToolbarItem
SearchBar
TextCell
(and hence alsoImageCell
)ListView
TapGestureRecognizer
除了SearchBar和ListView元素外,这些元素定义了两个属性(可绑定属性):
Command
of typeSystem.Windows.Input.ICommand
CommandParameter
of typeObject
SearchBar
定义了SearchCommand和
SearchCommandParameter
属性,而ListView
定义了 ICommand
类型的属性RefreshCommand。
ICommand
接口定义两个方法和一个事件:
void Execute(object arg) 该方法封装了操作本身
bool CanExecute(object arg) 指示是否可以调用该命令,false则置灰
event EventHandler CanExecuteChanged 在发生影响命令是否应执行的更改时引发
ViewModel可以定义ICommand类型的属性, 然后,可以将这些属性绑定到每个Button或其他元素的Command属性,或者绑定到实现此接口的自定义视图。 您可以选择设置CommandParameter属性以标识绑定到此ViewModel属性的单个Button对象(或其他元素)。 在内部,每当用户单击Button时,Button就会调用Execute方法,并将其CommandParameter传递给Execute方法。
CanExecute方法和CanExecuteChanged事件用于按钮单击当前可能无效的情况,在这种情况下,按钮应禁用自身。 首次设置Command属性时以及每当触发CanExecuteChanged事件时,Button都会调用CanExecute。 如果CanExecute返回false,则Button会自行禁用并且不会生成Execute调用。
为了帮助向ViewModel添加命令,Xamarin.Forms定义了两个实现ICommand的类:Command和Command <T>,其中T是Execute和CanExecute的参数类型(可为null)。 这两个类定义了几个构造函数,以及一个ChangeCanExecute方法,ViewModel可以调用该方法来强制Command对象触发CanExecuteChanged事件。
在视图模型内,对于ICommand类型的视图模型中的每个公共属性,应该有一个Command或Command <T>类型的对象。 Command或Command <T>构造函数需要一个Action回调对象,该对象在调用ICommand.Execute方法时被调用。 CanExecute方法是一个可选的构造函数参数,并且是一个返回布尔值的Func。
using System;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;
namespace XamlSamples
{
class KeypadViewModel : INotifyPropertyChanged
{
string inputString = "";
string displayText = "";
char[] specialChars = { '*', '#' };
public event PropertyChangedEventHandler PropertyChanged;
// Constructor
public KeypadViewModel()
{
AddCharCommand = new Command<string>((key) =>
{
// Add the key to the input string.
InputString += key;
});
DeleteCharCommand = new Command(() =>
{
// Strip a character from the input string.
InputString = InputString.Substring(0, InputString.Length - 1);
},
() =>
{
// Return true if there's something to delete.
return InputString.Length > 0;
});
}
// Public properties
public string InputString
{
protected set
{
if (inputString != value)
{
inputString = value;
OnPropertyChanged("InputString");
DisplayText = FormatText(inputString);
// Perhaps the delete button must be enabled/disabled.
((Command)DeleteCharCommand).ChangeCanExecute();
}
}
get { return inputString; }
}
public string DisplayText
{
protected set
{
if (displayText != value)
{
displayText = value;
OnPropertyChanged("DisplayText");
}
}
get { return displayText; }
}
// ICommand implementations
public ICommand AddCharCommand { protected set; get; }
public ICommand DeleteCharCommand { protected set; get; }
string FormatText(string str)
{
bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
string formatted = str;
if (hasNonNumbers || str.Length < 4 || str.Length > 10)
{
}
else if (str.Length < 8)
{
formatted = String.Format("{0}-{1}",
str.Substring(0, 3),
str.Substring(3));
}
else
{
formatted = String.Format("({0}) {1}-{2}",
str.Substring(0, 3),
str.Substring(3, 3),
str.Substring(6));
}
return formatted;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View Code
通过使用Command <T>类实例化命令,可以将参数传递给Execute和CanExecute操作。 例如,以下代码显示如何使用Command <T>实例指示NavigateAsync方法将需要字符串类型的参数。
public ICommand NavigateCommand => new Command<string>(NavigateAsync);
在Command和Command <T>类中,每个构造函数中CanExecute方法的委托都是可选的。 如果未指定委托,则Command将为CanExecute返回true。 但是,视图模型可以通过在Command对象上调用ChangeCanExecute方法来指示命令的CanExecute状态更改。 这将引发CanExecuteChanged事件。 然后,UI中与该命令绑定的所有控件都将更新其启用状态,以反映数据绑定命令的可用性。
调用异步方法
命令还可以调用异步方法。 这是通过在指定Execute方法时使用async和await关键字来实现的:
DownloadCommand = new Command (async () => await DownloadAsync ());
async Task DownloadAsync ()
{
await Task.Run (() => Download ());
}
void Download ()
{
...
}
View Code
XAML 控件
视图是用户界面对象,例如标签,按钮和滑块,在其他图形编程环境中通常称为控件或窗口小部件。 Xamarin.Forms支持的视图都是从View类派生的。
Xamarin.Forms中定义的所有视图都可以从XAML文件中引用
XAML编译
可以选择使用XAML编译器(XAMLC)将XAML直接编译为中间语言(IL)。
XAML编译具有许多优点:
- 它执行XAML的编译时检查,并通知用户任何错误。
- 它消除了XAML元素的某些加载和实例化时间。
- 它不再包含.xaml文件,有助于减小最终程序集的文件大小。
默认情况下,禁用XAML编译以确保向后兼容。 通过添加XamlCompilation属性,可以在程序集和类级别启用它。
以下代码示例演示了在程序集级别启用XAML编译:(AssemblyInfo.cs)
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
在此示例中,将对程序集中包含的所有XAML进行编译时检查,并在编译时而不是在运行时报告XAML错误。 XamlCompilation属性的前缀assembly 指定该属性适用于整个程序集。
下面的代码示例演示了在类级别启用 XAML 编译:
using Xamarin.Forms.Xaml;
...
[XamlCompilation (XamlCompilationOptions.Compile)]
public class HomePage : ContentPage
{
...
}
XAML工具
XAML热重载
XAML Hot Reload可插入您现有的工作流程中,以提高工作效率并节省时间。 如果没有XAML Hot Reload,则每次看到XAML更改时都必须构建和部署应用程序。 使用热重装,当您保存XAML文件时,更改将实时反映在您正在运行的应用程序中。 此外,您的导航状态和数据将得到维护,使您可以快速在UI上进行迭代,而不会丢失您在应用程序中的位置。 因此,借助XAML Hot Reload,您将花费更少的时间来重建和部署应用程序以验证UI更改。
启用
使用 XAML 热重载不需要额外的安装或设置。 它内置于 Visual Studio 中,并可在 IDE 设置中启用。 启用后,可以通过在模拟器、模拟器或物理设备上调试应用,开始使用 XAML 热重载。
On Windows, XAML Hot Reload can be enabled by checking the Enable Xamarin Hot Reload checkbox at Tools > Options > Xamarin > Hot Reload.
弹性加载
如果您更改了XAML 但Hot Reload无法重新加载,它将使用IntelliSense向您显示一条错误消息。 这些更改称为粗鲁的编辑,包括错误地输入XAML或将控件连接到不存在的事件处理程序。 即使进行粗鲁的编辑,您也可以继续重新加载而无需重新启动应用程序-在XAML文件中的其他位置进行其他更改,然后单击保存。 粗鲁的编辑将不会重新加载,但您的其他更改将继续应用。
已知的限制
- 在 XAML 热重载会话期间,无法添加、删除或重命名文件或 NuGet 包。 如果添加或删除文件或 NuGet 包,请重新生成并重新部署应用,以便继续使用 XAML 热重载。
- 将链接器设置为 "不链接" 以获得最佳体验。 仅限链接 SDK设置工作正常,但在某些情况下可能会失败。
- 在物理 iPhone 上调试需要解释器使用 XAML 热重载。 向 iOS 生成设置中的 "其他 mtouch 参数" 字段添加 --解释器以使用 XAML 热重载。
- 通过使用
x:Name
值将控件分配给另一个字段或属性而创建的任何引用都不会重新加载。 - 在AppShell中更新 Shell 应用程序的可视层次结构可能会导致应用程序的状态保持有问题。 重新生成应用程序以继续重新加载。
- XAML 热重载无法重新C#加载代码,包括事件处理程序、自定义控件、页代码隐藏以及其他类。
XAML预览程序
设置:Tools > Options > Xamarin > Forms Previewer
在非程序运行而是在编辑时,XAML 预览器会显示你的 Xamarin. Forms XAML 页面在 iOS 和 Android 上的外观。 当你对 XAML 进行更改时, 你会立即看到它们与你的代码一起改变。
设计时数据
没有数据,某些布局很难可视化。 使用这些技巧可以最大程度地利用XAML预览器中的数据量繁重的页面。
设计时数据是为使控件在XAML预览器中更容易可视化而设置的伪造数据。 首先,将以下代码行添加到XAML页面的标题中:
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
添加名称空间后,可以将d:放在任何属性或控件的前面,以在XAML预览器中显示它。 带有d:的元素不会在运行时显示。
例如,您可以将文本添加到通常绑定了数据的标签。
<Label Text="{Binding Name}" d:Text="Name!" />
可以将d:与Xamarin.Forms控件的任何属性一起使用,例如颜色,字体大小和间距。 您甚至可以将其添加到控件本身:
<d:Button Text="Design Time Button" />
在此示例中,按钮仅在设计时出现。 使用此方法将占位符放入XAML预览器不支持的自定义控件。
如果不想将设计时数据添加到各个控件,可以设置模拟数据存储以绑定到页面。
命名空间
其他功能
1、可绑定属性
在Xamarin.Forms中,可绑定属性扩展了公共语言运行时(CLR)属性的功能。 可绑定属性是一种特殊的属性,其中Xamarin.Forms属性系统会跟踪该属性的值。 本文提供了可绑定属性的介绍,并演示了如何创建和使用它们。
可绑定属性通过备份具有BindableProperty类型的属性,而不是通过字段支持属性来扩展CLR属性功能。
可绑定属性的目的是提供一个属性系统,该系统支持通过父子关系设置的数据绑定,样式,模板和值。 此外,可绑定属性可以提供默认值,属性值验证以及监视属性更改的回调。
应将属性实现为可绑定属性,以支持以下一项或多项功能:
- 充当数据绑定的有效目标属性。
- 通过样式设置属性。
- 提供与属性类型的默认值不同的默认属性值。
- 验证属性的值。
- 监视属性更改。
Xamarin.Forms可绑定属性的示例包括Label.Text,Button.BorderRadius和StackLayout.Orientation。 每个可绑定属性都有一个对应的BindableProperty类型的公共静态只读属性,该属性公开在同一类上,并且是可绑定属性的标识符。 例如,Label.Text属性的相应可绑定属性标识符是Label.TextProperty。
1.1、创建和使用可绑定属性
步骤:
1、使用BindableProperty.Create方法重载之一创建BindableProperty实例。
2、为BindableProperty实例 定义属性访问器(get、set)
请注意,必须在UI线程上创建所有BindableProperty实例。 这意味着只有在UI线程上运行的代码才能获取或设置可绑定属性的值。 但是,可以使用Device.BeginInvokeOnMainThread方法将其(BindableProperty)绑定到UI线程,从而从其他线程访问BindableProperty实例。
1.2、创建属性
要创建BindableProperty实例,包含类必须从BindableObject类派生。 但是,BindableObject类在类层次结构中较高,因此用于用户界面功能的大多数类都支持可绑定属性。
定义如下:
创建BindableProperty时,至少必须指定一个标识符【下面的EventNameProperty】以及以下参数:
public static readonly BindableProperty EventNameProperty =
BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null);
这将创建一个名为EventName的BindableProperty实例,其类型为string,该属性归EventToCommandBehavior类所有,其默认值为null。
可绑定属性的命名约定是,可绑定属性标识符必须与Create方法中指定的属性名称匹配,并在属性名称后附加“ Property”。 因此,在上面的示例中,可绑定属性标识符是EventNameProperty。
当然还可以指定其他参数:
1、检测属性更改:propertyChanged参数,可以将静态属性已更改的回调方法注册为可绑定属性,当bindable属性的值更改时,将调用指定的回调方法。
以下将OnEventNameChanged方法注册为属性更改的回调方法:
public static readonly BindableProperty EventNameProperty =
BindableProperty.Create (
"EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
...
static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
}
View Code
2、验证回调:validateValue参数
指定validateValue参数,可以向可绑定属性注册静态验证回调方法,设置bindable属性的值时,将调用指定的回调方法。
下面的代码示例演示Angle可绑定属性如何将IsValidValue方法注册为验证回调方法:
public static readonly BindableProperty AngleProperty =
BindableProperty.Create ("Angle", typeof(double), typeof(HomePage), 0.0, validateValue: IsValidValue);
...
static bool IsValidValue (BindableObject view, object value)
{
double result;
bool isDouble = double.TryParse (value.ToString (), out result);
return (result >= 0 && result <= 360);
}
View Code
验证回调具有一个值,如果该值对该属性有效,则应返回true,否则返回false。 如果验证回调返回false,则将引发异常,此异常应由开发人员处理。
验证回调方法的典型用法是在设置bindable属性时限制整数或双精度值。 例如,IsValidValue方法检查属性值是否为0到360范围内的双精度值。
.......
1.3、创建访问器
属性访问器需要使用属性语法来访问可绑定的属性。
Get
访问器应返回相应的可绑定属性中包含的值,这可以通过调用GetValue方法,传入要获取值的可绑定属性标识符,然后将结果转换为所需的类型来实现。
Set访问器应设置相应可绑定属性的值,这可以通过调用SetValue方法,传入要设置值的可绑定属性标识符以及要设置的值来实现。
public string EventName {
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}
1.4、使用可绑定属性
一旦创建可绑定属性后,可以从 XAML 或代码使用它。
<ContentPage ... xmlns:local="clr-namespace:EventToCommandBehavior" ...>
...
<ListView ...>
<ListView.Behaviors>
<local:EventToCommandBehavior EventName="ItemSelected" ... />
</ListView.Behaviors>
</ListView>
View Code
2、附加属性
附加属性是一种特殊的可绑定属性,在一个类中定义但附加到其他对象,并且在XAML中可识别为包含类和以句点分隔的属性名称的属性。
附加属性 使对象可以为其自己的类未定义的属性分配值。 例如,子元素可以使用附加的属性来通知其父元素如何在用户界面中呈现它们。Grid控件允许通过设置Grid.Row和Grid.Column附加属性来指定子项的行和列。Grid.Row和Grid.Column是附加属性,因为它们是在作为Grid的子元素的元素上设置的,而不是在Grid本身上设置的。
在以下情况下,应将可绑定属性实现为附加属性:
- 需要除定义类之外的其他类可使用的属性设置机制。
- 当类表示需要与其他类轻松集成的服务时。
2.1、创建和使用附加的属性
- 使用CreateAttached方法重载之一创建BindableProperty实例。
- 提供静态GetPropertyName和SetPropertyName方法作为附加属性的访问器。
创建用于其他类型的附加属性时,创建属性的类不必从BindableObject派生。 但是,访问器的目标属性应该是BindableObject或从BindableObject派生。
可以通过声明BindableProperty类型的公共静态只读属性来创建附加属性。 应将bindable属性设置为BindableProperty.CreateAttached方法重载之一的返回值。 该声明应在所属类的主体之内,但不属于任何成员定义。
以下代码显示附加属性的示例:
public static readonly BindableProperty HasShadowProperty =
BindableProperty.CreateAttached ("HasShadow", typeof(bool), typeof(ShadowEffect), false);
访问器
需要使用静态GetPropertyName和SetPropertyName方法作为附加属性的访问器,否则属性系统将无法使用附加属性。 GetPropertyName访问器应符合以下签名:
public static valueType GetPropertyName(BindableObject target)
GetPropertyName访问器应返回附加属性的相应BindableProperty字段中包含的值,这可以通过调用GetValue方法,传入要获取值的可绑定属性标识符,然后将结果值转换为所需的类型来实现。
public static bool GetHasShadow (BindableObject view)
{
return (bool)view.GetValue (HasShadowProperty);
}
public static void SetHasShadow (BindableObject view, bool value)
{
view.SetValue (HasShadowProperty, value);
}
典型用法,效果。
3、资源字典 ResourceDictionary
XAML资源是可以在整个Xamarin.Forms应用程序中共享和重用的对象的定义,这些资源对象存储在资源字典中。
ResourceDictionary是Xamarin.Forms应用程序使用的资源的存储库,存储在ResourceDictionary中的典型资源包括样式、控件模板、数据模板、颜色和转换器。
在XAML中,然后可以使用StaticResource标记扩展来检索存储在ResourceDictionary中的资源并将其应用于元素。
在C#中,也可以在ResourceDictionary中定义资源,然后使用基于字符串的索引器来检索资源并将其应用于元素,但是,在C#中使用ResourceDictionary几乎没有优势,因为共享对象可以简单地存储为字段或属性,并且可以直接访问而无需先从字典中检索它们。
创建和使用 ResourceDictionary
资源在ResourceDictionary中定义,然后将其设置为以下“资源”属性之一:
- 从Application派生的任何类的Resources属性
- 从VisualElement派生的任何类的Resources属性
Xamarin.Forms程序仅包含一个从Application派生的类,但经常使用许多从VisualElement派生的类,包括页面,布局和控件。这些对象中的任何一个都可以将其Resources属性设置为ResourceDictionary。选择放置特定ResourceDictionary的位置会影响可以使用资源的位置:
- 附加到诸如Button或Label之类的视图的ResourceDictionary中的资源只能应用于该特定对象,因此这不是很有用。
- 附加到诸如StackLayout或Grid之类的布局的ResourceDictionary中的资源可以应用于该布局以及该布局的所有子级。
- 在页面级别定义的ResourceDictionary中的资源可以应用于页面及其所有子级。
- 在应用程序级别定义的ResourceDictionary中的资源可以在整个应用程序中应用。
<Application ...>
<Application.Resources>
<ResourceDictionary>
<Color x:Key="PageBackgroundColor">Yellow</Color>
<Color x:Key="HeadingTextColor">Black</Color>
<Color x:Key="NormalTextColor">Blue</Color>
<Style x:Key="LabelPageHeadingStyle" TargetType="Label">
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="TextColor" Value="{StaticResource HeadingTextColor}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
此ResourceDictionary定义了三个Color资源和一个Style资源。
注:特定于单个页面的资源不应包含在应用程序级资源字典中,因为这样的资源将在应用程序启动时进行解析,而不是在页面需要时进行解析。 有关更多信息,请参见减小应用程序资源字典大小。
资源覆盖
当ResourceDictionary资源共享x:Key属性值时,在视图层次结构中定义较低的资源将优先于定义较高的资源。 例如,在应用程序级别将PageBackgroundColor资源设置为Blue 将被设置为Yellow的页面级别PageBackgroundColor资源覆盖。 同样,页面级别的PageBackgroundColor资源将被控件级别的PageBackgroundColor资源覆盖。
考虑ResourceDictionary优先级的另一种方法:当XAML解析器遇到StaticResource时,它将使用找到的第一个匹配项,通过在可视树中向上移动来搜索匹配项。 如果此搜索在页面结束,并且仍未找到键,则XAML解析器将搜索附加到App对象的ResourceDictionary。 如果仍然找不到该键,则会引发异常。
独立的资源字典
从ResourceDictionary派生的类也可以位于单独的独立文件中。 (更确切地说,从ResourceDictionary派生的类通常需要一对文件,因为资源是在XAML文件中定义的,但是还需要一个带有InitializeComponent调用的代码隐藏文件。)然后,可以在应用程序之间共享结果文件。
要创建这样的文件,请将新的“内容视图”或“内容页面”项添加到项目中(而不是仅包含C#文件的“内容视图”或“内容页面”)。 在XAML文件和C#文件中,将基类的名称从ContentView或ContentPage更改为ResourceDictionary。 在XAML文件中,基类的名称是顶级元素。
合并资源字典到xaml文件
合并的资源字典将一个或多个ResourceDictionary对象组合到另一个ResourceDictionary中。
合并本地资源字典
通过将Source属性设置为带有资源的XAML文件的文件名,可以将本地ResourceDictionary合并到另一个ResourceDictionary中:
<ContentPage ...>
<ContentPage.Resources>
<!-- Add more resources here -->
<ResourceDictionary Source="MyResourceDictionary.xaml" />
<!-- Add more resources here -->
</ContentPage.Resources>
...
</ContentPage>
此语法不会实例化MyResourceDictionary类。 而是,它引用XAML文件。 因此,在设置Source属性时,不需要代码隐藏文件(MyResourceDictionary.xaml.cs),并且可以从MyResourceDictionary.xaml文件的根标记中删除x:Class属性。 另外,当使用这种方法合并资源字典时,Xamarin.Forms将自动实例化ResourceDictionary,因此不需要外部ResourceDictionary标记。