第 1 部分。 XAML 入门

Download Sample下载示例

在 Xamarin.Forms 应用程序中,XAML 主要用于定义页面的视觉内容,并与 C# 代码隐藏文件协同工作。

代码隐藏文件为标记提供代码支持。 这两个文件共同构成一个包含子视图和属性初始化的新的类定义。 在 XAML 文件中,使用 XML 元素和特性引用类和属性,并在标记和代码之间建立链接。

创建解决方案

要开始编辑第一个 XAML 文件,请使用 Visual Studio 或 Visual Studio for Mac 创建新的 Xamarin.Forms 解决方案。 (选择下面与环境对应的选项卡。)

在 Windows 中,启动 Viual Studio 2019,然后在开始窗口中单击“创建新项目”以创建新项目

New Solution Window

在“创建新项目”窗口中,在“项目类型”下拉菜单中选择“移动”,选择“移动应用(Xamarin.Forms)”,然后单击“下一步”按钮:

New Project Window

在“配置新项目”窗口中,将“项目名称”设置为 XamlSamples(或任何喜欢的名称),然后单击“创建”按钮。

在“新的跨平台应用”对话框中,单击“空白”,然后单击“确定”按钮:

New App Dialog

解决方案中将会创建四个项目:XamlSamples .NET Standard 库、XamlSamples.Android、XamlSamples.iOS 和通用 Windows 平台解决方案 XamlSamples.UWP。

创建 XamlSamples 解决方案后,可能需要通过选择各种平台项目作为解决方案启动项目,在手机仿真器或实际设备上生成和部署根据项目模板创建的简单应用程序来测试开发环境。

除非需要编写特定于平台的代码,否则共享的 XamlSamples .NET Standard 库项目几乎就是你进行编程的唯一位置。 这些文章不会设计该项目以外的内容。

XAML 文件的剖析

在 XamlSamples .NET Standard 库,存在一对具有以下名称的文件:

  • App.xaml(XAML文件);以及
  • App.xaml.cs(与 XAML 文件关联的 C# 代码隐藏文件)。

需要单击 App.xaml 旁边的箭头才能查看代码隐藏文件。

App.xamlApp.xaml.cs 都构成名为 App 的类(派生自 Application)。 大多数包含 XAML 文件的其他类都促成了派生自 ContentPage 的类;这些文件使用 XAML 定义整个页面的视觉内容。 XamlSamples 项目中的其他两个文件也是如此:

  • MainPage.xaml(XAML文件)和
  • MainPage.xaml.cs(C# 代码隐藏文件)。

MainPage.xaml 文件外观如下所示(尽管格式设置可能略有不同):

<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>

两个 XML 命名空间(xmlns)声明引用了 URI,第一个似乎位于 Xamarin 的网站上,第二个似乎位于 Microsoft 的网站上。 不必费心检查这些 URI 指向的内容。 那里什么都没有。 它们只是 Xamarin 和 Microsoft 拥有的 URI,它们基本上充当版本标识符。

第一个 XML 命名空间声明表示在 XAML 文件中定义的不带前缀的标记引用 Xamarin.Forms 中的类,例如 ContentPage。 第二个命名空间声明定义 x 的前缀。 该前缀用于 XAML 自身固有的多个元素和特性,XAML 的其他实现也支持这些元素和特性。 但这些元素和特性会有所不同,具体取决于在 URI 中嵌入的年份。 Xamarin.Forms 支持 2009 XAML 规范,但并非支持其全部内容。

local 命名空间声明允许从 .NET Standard 库项目访问其他类。

在该第一个标记的末尾,前缀 x 用于名为 Class 的属性。 由于 x 前缀的使用在 XAML 命名空间中基本上很普遍,因此 Class 等 XAML 特性几乎总是被称为 x:Class

x:Class 特性指定完全限定的 .NET 类名:XamlSamples 命名空间中的 MainPage 类。 这意味着,此 XAML 文件定义了一个在 XamlSamples 命名空间中名为 MainPage 的新类,它派生自 ContentPage,即 x:Class 属性所在的标记。

x:Class 特性只能出现在 XAML 文件的根元素中,用于定义派生的 C# 类。 这是 XAML 文件中定义的唯一新类。 XAML 文件中显示的其他所有内容只是从现有类实例化并初始化的。

MainPage.xaml.cs 文件的外观如下所示(除未使用的 using 指令以外):

using Xamarin.Forms;

namespace XamlSamples
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

MainPage 类派生自 ContentPage,但请注意 partial 类定义。 这表明 MainPage 应该存在另一个分部类定义,但它在哪里? InitializeComponent 方法又是什么?

当 Visual Studio 生成项目时,它会分析 XAML 文件以生成 C# 代码文件。 如果在 XamlSamples\XamlSamples\obj\Debug 目录中查找,你将找到一个名为 XamlSamples.MainPage.xaml.g.cs 的文件。 “g”表示生成的。 这是 MainPage 的另一个分部类定义,其中包含从 MainPage 构造函数调用的 InitializeComponent 方法的定义。 然后,可以将这两个分部 MainPage 类定义一起编译。 根据 XAML 是否已编译,在可执行文件中嵌入该 XAML 文件或该 XAML 文件的二进制形式。

在运行时,特定平台项目中的代码会调用 LoadApplication 方法,并将其传递给 .NET Standard 库中 App 类的新实例。 App 类构造函数会将 MainPage 实例化。 该类的构造函数会调用 InitializeComponent,然后它会调用从 .NET Standard 库中提取 XAML 文件(或其已编译的二进制文件)的 LoadFromXaml 方法。 LoadFromXaml 会初始化 XAML 文件中定义的所有对象,将它们按父子关系连接在一起,将代码中定义的事件处理程序附加到 XAML 文件中设置的事件,并将生成的对象树设置为页面的内容。

虽然通常不需要花太多时间处理生成的代码文件,但有时所生成文件的代码上会引发运行时异常,因此你应该熟悉它们。

编译并运行此程序时,Label 元素会按 XAML 建议显示在页面的中心:

Default Xamarin.Forms display

要获得更有趣的视觉效果,只需制作更有趣的 XAML。

添加新的 XAML 页面

要向项目添加其他基于 XAML 的 ContentPage 类,请选择“XamlSamples”.NET Standard 库项目,右键单击并选择“添加 > 新项目...”。在“添加新项”对话框中,选择“Visual C# 项 >Xamarin.Forms> 内容页”(而不是创建仅代码页的“内容页 [C#]”,也不是“内容视图”[它不是页面])。 为页面命名,例如 HelloXamlPage:

Add New Item Dialog

将两个文件添加到项目,即 HelloXamlPage.xaml 和代码隐藏文件 HelloXamlPage.xaml.cs。

设置页面内容

编辑 HelloXamlPage.xaml 文件,以便用于 ContentPageContentPage.Content 的标记是唯一的标记:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.HelloXamlPage">
    <ContentPage.Content>

    </ContentPage.Content>
</ContentPage>

ContentPage.Content 标记是 XAML 的唯一语法的一部分。 它们起初可能似乎无效的 XML,但它们是合法的。 句点不是 XML 中的特殊字符。

ContentPage.Content 标记称为属性元素标记。 ContentContentPage 的属性,通常设置为单个视图或具有子视图的布局。 通常,属性将会成为 XAML 中的属性,但很难将 Content 属性设置为复杂对象。 因此,该属性表示为由类名和以句点分隔的属性名称所组成的 XML 元素。 现在,可以在 ContentPage.Content 标记之间设置 Content 属性,如下所示:

<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>

另请注意,根标记上已经设置了 Title 属性。

此时,类、属性和 XML 之间的关系应显而易见:Xamarin.Forms 类(如 ContentPageLabel)以 XML 元素的形式出现在 XAML 文件中。 该类的属性(包括 ContentPage 上的 TitleLabel 的七个属性)通常显示为 XML 属性。

有很多快捷方式可以设置这些属性的值。 一些属性是基本数据类型:例如,TitleText 为类型 StringRotation 为类型 DoubleIsVisible(默认为 true 且在此处仅设置用于插图)为类型 Boolean

HorizontalTextAlignment 属性是 TextAlignment 类型,是一种枚举。 对于枚举类型的任何属性,只需提供成员名称即可。

但对于更复杂类型的属性,需要使用转换器来分析 XAML。 这些是派生自 TypeConverter 的 Xamarin.Forms 中的类。 其中许多属于公共类,但有些不是。 对于此特定 XAML 文件,其中几个类在后台发挥作用:

  • VerticalOptions 属性的 LayoutOptionsConverter
  • FontSize 属性的 FontSizeConverter
  • TextColor 属性的 ColorTypeConverter

这些转换器控制属性设置的允许语法。

ThicknessTypeConverter 可以处理以逗号分隔的一个、两个或四个数字。 如果提供了一个数字,则它适用于所有四边。 如果提供两个数字,则第一个是左填充和右填充,第二个是顶部和底部。 如果提供四个数字,则按左、上、右和下顺序。

LayoutOptionsConverter 可以将 LayoutOptions 结构公共静态字段的名称转换为类型为 LayoutOptions 的值。

FontSizeConverter 可以处理 NamedSize 成员或数字字号。

ColorTypeConverter 接受 Color 结构的公共静态字段名称或十六进制 RGB 值(带或不带 alpha 通道),前面添加数字标志 (#)。 下面是不带 alpha 通道的语法:

TextColor="#rrggbb"

每个小字母都是一个十六进制数字。 下面介绍如何包括 alpha 通道:

TextColor="#aarrggbb">

对于 alpha 通道,请记住 FF 为完全不透明,00 为完全透明。

如果使用另外两种格式,则可以为每个通道仅指定单个十六进制数字:

TextColor="#rgb" TextColor="#argb"

在这些情况下,数字会重复以形成值。 例如,#CF3 表示 RGB 颜色 CC-FF-33。

运行 XamlSamples 程序时,将会显示 MainPage。 要查看新建的 HelloXamlPage,可以将它设置为 App.xaml.cs 文件中的新启动页,也可以从 MainPage 导航到新页面。

要实现导航,请先更改 App.xaml.cs 构造函数中的代码,以便创建对象 NavigationPage

public App()
{
    InitializeComponent();
    MainPage = new NavigationPage(new MainPage());
}

在 MainPage.xaml.cs 构造函数中,可以创建一个简单的 Button,然后使用事件处理程序导航到 HelloXamlPage

public MainPage()
{
    InitializeComponent();

    Button button = new Button
    {
        Text = "Navigate!",
        HorizontalOptions = LayoutOptions.Center,
        VerticalOptions = LayoutOptions.Center
    };

    button.Clicked += async (sender, args) =>
    {
        await Navigation.PushAsync(new HelloXamlPage());
    };

    Content = button;
}

设置页面的 Content 属性将替换 XAML 文件中 Content 属性的设置。 编译并部署此程序的新版本时,屏幕上会显示一个按钮。 按它可导航到 HelloXamlPage。 下面是 iPhone、Android 和 UWP 上生成的页面:

Rotated Label Text

在 iOS 上,可以使用“< 后退”按钮,在 Android 上请使用页面顶部或手机底部的向左箭头,在 Windows 10 上则使用页面顶部的向左箭头导航回到 MainPage

可以随意以不同的方式尝试使用 XAML 呈现 Label。 如果需要将任何 Unicode 字符嵌入到文本中,可以使用标准 XML 语法。 例如,要将问候语放在智能引号中,请使用:

<Label Text="&#x201C;Hello, XAML!&#x201D;" … />

其外观如下所示:

Rotated Label Text with Unicode Characters

XAML 和代码交互

HelloXamlPage 示例只包含页面上的一个 Label 示例,但这很不常见。 大多数 ContentPage 派生项会将 Content 属性设置为某种类型的布局,例如 StackLayoutStackLayoutChildren 属性定义为类型 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>

此 XAML 文件在语法上是完整的,其外观如下所示:

Multiple Controls on a Page

但你可能会认为此程序在功能上不足。 也许 Slider 应该会导致 Label 显示当前值,并且 Button 可能打算在程序中执行某些操作。

第 4 部分数据绑定基础知识所示,可以使用数据绑定完全在 XAML 中处理使用 Label 值显示 Slider 值的作业。 但首先查看代码解决方案会非常有用。 即便如此,要实现 Button 单击必然需要代码。 这意味着,XamlPlusCodePage 的代码隐藏文件必须包含 SliderValueChanged 事件的处理程序,以及 ButtonClicked 事件的处理程序。 我们来添加它们:

namespace XamlSamples
{
    public partial class XamlPlusCodePage
    {
        public XamlPlusCodePage()
        {
            InitializeComponent();
        }

        void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
        {

        }

        void OnButtonClicked(object sender, EventArgs args)
        {

        }
    }
}

这些事件处理程序不需要是公共的。

回到 XAML 文件中,SliderButton 标记需要包含引用这些处理程序的 ValueChangedClicked 事件的特性:

<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"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="A simple Label"
               Font="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Click Me!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="OnButtonClicked" />
    </StackLayout>
</ContentPage>

请注意,为事件分配处理程序和为属性分配值所用语法相同。

如果 SliderValueChanged 事件的处理程序将使用 Label 来显示当前值,则处理程序需要从代码引用该对象。 Label 需要一个使用 x:Name 特性指定的名称。

<Label x:Name="valueLabel"
       Text="A simple Label"
       Font="Large"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand" />

x:Name 特性的 x 前缀指示此特性是 XAML 固有的。

分配给 x:Name 特性的名称与 C# 变量名称所用规则相同。 例如,必须以字母或下划线开头,且不包含嵌入的空格。

现在,ValueChanged 事件处理程序可以设置 Label 以显示新的 Slider 值。 新值可从事件参数获取:

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
    valueLabel.Text = args.NewValue.ToString("F3");
}

或者,处理程序可以获取从 sender 参数生成此事件的 Slider 对象,并从中获取 Value 属性:

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
    valueLabel.Text = ((Slider)sender).Value.ToString("F3");
}

首次运行程序时,Label 不会显示 Slider 值,因为尚未触发 ValueChanged 事件。 但是,Slider 的任何操作都会导致显示值:

Slider Value Displayed

现在设置 Button。 我们通过显示带有按钮 Text 的警报来模拟对 Clicked 事件的响应。 事件处理程序可以安全地将 sender 参数强制转换为 Button,然后访问其属性:

async void OnButtonClicked(object sender, EventArgs args)
{
    Button button = (Button)sender;
    await DisplayAlert("Clicked!",
        "The button labeled '" + button.Text + "' has been clicked",
        "OK");
}

该方法被定义为 async,因为 DisplayAlert 方法是异步的,并且应以 await 运算符开头,它会在方法完成时返回。 因为此方法从 sender 参数获取触发事件的 Button,所以同一个处理程序可用于多个按钮。

你已经看到,在 XAML 中定义的对象可以触发在代码隐藏文件中处理的事件,并且代码隐藏文件可以使用通过 x:Name 属性分配给对象的名称来访问 XAML 中定义的对象。 这些是代码和 XAML 交互的两种基本方法。

通过检查新生成的 XamlPlusCode.xaml.g.cs 文件(它现在包括作为专用字段分配给任何 x:Name 属性的任何名称),可以收集有关 XAML 工作原理的一些其他见解。 下面是该文件的简化版本:

public partial class XamlPlusCodePage : ContentPage {

    private Label valueLabel;

    private void InitializeComponent() {
        this.LoadFromXaml(typeof(XamlPlusCodePage));
        valueLabel = this.FindByName<Label>("valueLabel");
    }
}

此字段的声明允许在你管辖范围内 XamlPlusCodePage 分部类文件中的任意位置自由使用该变量。 在运行时,将在分析 XAML 后分配该字段。 这意味着当 XamlPlusCodePage 构造函数开始时,valueLabel 字段为 null,但调用 InitializeComponent 后将会生效。

InitializeComponent 将控件返回到构造函数后,页面的视觉对象已像在代码中实例化和初始化一样构造。 XAML 文件在类中将不再起任何作用。 例如,可以通过向 StackLayout 添加视图或将页面的 Content 属性完全设置为其他内容以任何所需方式操作页面上的这些对象。 可以通过检查页面的 Content 属性和布局 Children 集合中的项目来“遍历树”。 可以设置以这种方式访问的视图的属性,或动态为其分配事件处理程序。

请随意操作。 它是你的页面,XAML 只是生成其内容的工具。

总结

通过此简介,你已了解 XAML 文件和代码文件如何参与类定义,以及 XAML 和代码文件如何交互。 但 XAML 也有自己的独特的语法功能,这允许它以非常灵活的方式使用。 可以在第 2 部分基本 XAML 语法中开始探索这些内容。