第 2 部分。 基本 XAML 语法

XAML 主要用于实例化和初始化对象。 但通常,必须将属性设置为无法轻易表示为 XML 字符串的复杂对象,有时必须在子类上设置由一个类定义的属性。 对于这两项要求,需要用到属性元素和附加属性的基本 XAML 语法功能。

属性元素

在 XAML 中,类的属性通常设置为 XML 特性:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large"
       TextColor="Aqua" />

但是,有一种在 XAML 中设置属性的替代方法。 若要尝试使用 TextColor 的替代方法,请先删除现有 TextColor 设置:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large" />

通过将空元素 Label 标记分离为开始标记和结束标记,将其打开:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">

</Label>

在这些标记中,添加由类名和由句点分隔的属性名称组成的开始和结束标记:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">
    <Label.TextColor>

    </Label.TextColor>
</Label>

将属性值设置为这些新标记的内容,如下所示:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">
    <Label.TextColor>
        Aqua
    </Label.TextColor>
</Label>

这两种指定 TextColor 属性的方法在功能上是等效的,但不要对同一属性使用这两种方法,因为这样可以有效地设置属性两次,并且可能会产生歧义。

使用此新语法,可以引入一些方便的术语:

  • Label 是一个对象元素。 它是表示为 XML 元素的 Xamarin.Forms 对象。
  • TextVerticalOptionsFontAttributesFontSize 是属性特性。 它们是表示为 XML 特性的 Xamarin.Forms 属性。
  • 在该最后的代码片段中,TextColor 已成为属性元素。 它是 Xamarin.Forms 属性,但它现在是 XML 元素。

属性元素的定义起初似乎是 XML 语法的冲突,但其实不是。 句点在 XML 中没有特殊含义。 对于 XML 解码器,Label.TextColor 只是普通子元素。

但是,在 XAML 中,此语法非常特殊。 属性元素的规则之一是 Label.TextColor 标记中没有其他任何内容。 属性的值始终定义为属性元素开始标记和结束标记之间的内容。

可以在多个属性上使用属性元素语法:

<Label Text="Hello, XAML!"
       VerticalOptions="Center">
    <Label.FontAttributes>
        Bold
    </Label.FontAttributes>
    <Label.FontSize>
        Large
    </Label.FontSize>
    <Label.TextColor>
        Aqua
    </Label.TextColor>
</Label>

或者,可以对所有属性使用属性元素语法:

<Label>
    <Label.Text>
        Hello, XAML!
    </Label.Text>
    <Label.FontAttributes>
        Bold
    </Label.FontAttributes>
    <Label.FontSize>
        Large
    </Label.FontSize>
    <Label.TextColor>
        Aqua
    </Label.TextColor>
    <Label.VerticalOptions>
        Center
    </Label.VerticalOptions>
</Label>

起初,属性元素语法可能看起来是对相对简单的事物进行了不必要的冗长替换,在这些例子中确实如此。

但是,当属性的值太复杂,无法表示为简单字符串时,属性元素语法就变得至关重要。 在属性-元素标记中,可以实例化另一个对象并设置其属性。 例如,可以使用属性设置显式将属性(例如 VerticalOptions )设置为 LayoutOptions 值:

<Label>
    ...
    <Label.VerticalOptions>
        <LayoutOptions Alignment="Center" />
    </Label.VerticalOptions>
</Label>

另一个示例:Grid 有两个名为 RowDefinitionsColumnDefinitions 的属性。 这两个属性的类型为 RowDefinitionCollectionColumnDefinitionCollection,它们是 RowDefinitionColumnDefinition 对象的集合。 需要使用属性元素语法来设置这些集合。

下面是 GridDemoPage 类的 XAML 文件的开头,其中显示了 RowDefinitionsColumnDefinitions 集合的属性元素标记:

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

请注意用于定义自动调整大小的单元格、像素宽度和高度的单元格以及星形设置的缩写语法。

附加属性

你刚刚看到,Grid 需要 RowDefinitionsColumnDefinitions 集合的属性元素来定义行和列。 但是,程序员还必须有某种方法来指示 Grid 的每个子元素所在的行和列。

Grid 的每个子项的标记中,使用以下特性指定该子项的行和列:

  • Grid.Row
  • Grid.Column

这些特性的默认值为 0。 还可以指示子级是否跨多个行或列,其中包含以下特性:

  • Grid.RowSpan
  • Grid.ColumnSpan

这两个特性的默认值为 1。

下面是完整的 GridDemoPage.xaml 文件:

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

不需要 0 的 Grid.RowGrid.Column 设置,但为了清晰起见,通常会包括这些设置。

如下所示:

网格布局

从语法来看,这些 Grid.RowGrid.ColumnGrid.RowSpanGrid.ColumnSpan 特性似乎是静态字段或 Grid 的属性,但有趣的是,Grid 没有定义任何命名为 RowColumnRowSpanColumnSpan 的内容。

相反,Grid 定义四个名为 RowPropertyColumnPropertyRowSpanPropertyColumnSpanProperty 的可绑定属性。 这些是称为附加属性的特殊类型的可绑定属性。 它们由 Grid 类定义,但在 Grid 的子元素上设置。

如果要在代码中使用这些附加属性,Grid 类提供名为 SetRowGetColumn等的静态方法。 但在 XAML 中,这些附加属性设置为 Grid 的自元素中的特性,并使用简单属性名称。

附加属性在 XAML 文件中始终可识别为包含类名和属性名的由句点分隔的特性。 它们被称为附加属性,因为它们由一个类定义(本例中为 Grid),但附加到其他对象(本例中为 Grid 的子对象)。 在布局期间,Grid 可以查询这些附加属性的值,以了解放置每个子元素的位置。

AbsoluteLayout 类定义两个名为 LayoutBoundsLayoutFlags 的附加属性。 下面是使用 AbsoluteLayout 的比例定位和大小调整特征实现的棋盘模式:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.AbsoluteDemoPage"
             Title="Absolute Demo Page">

    <AbsoluteLayout BackgroundColor="#FF8080">
        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.33, 0, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="1, 0, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0, 0.33, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.67, 0.33, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.33, 0.67, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="1, 0.67, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0, 1, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.67, 1, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

  </AbsoluteLayout>
</ContentPage>

如下所示:

绝对布局

对于此类内容,你可能会质疑使用 XAML 是否明智。 当然,从 LayoutBounds 矩形的重复性和规律性来看,用代码来实现可能会更好。

这确实是一个合理的担忧,并且在定义用户界面时,平衡使用代码和标记是没有问题的。 可以轻松地在 XAML 中定义某些视觉对象,然后使用代码隐藏文件的构造函数添加更多可能在循环中生成的视觉对象。

内容属性

在前面的示例中,StackLayoutGridAbsoluteLayout 对象设置为 ContentPageContent 属性,这些布局的子级实际上是 Children 集合中的项。 然而,这些 ContentChildren 属性在 XAML 文件中并不存在。

当然,可以包括 ContentChildren 属性作为属性元素,例如在 XamlPlusCode 示例中:

<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">
    <ContentPage.Content>
        <StackLayout>
            <StackLayout.Children>
                <Slider VerticalOptions="CenterAndExpand"
                        ValueChanged="OnSliderValueChanged" />

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

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

真正的问题是:为什么 XAML 文件中需要这些属性元素?

允许在 XAML 中使用的 Xamarin.Forms 中定义的元素在类的 ContentProperty 特性中标记一个属性。 如果在联机 Xamarin.Forms 文档中查找 ContentPage 类,你将看到此特性:

[Xamarin.Forms.ContentProperty("Content")]
public class ContentPage : TemplatedPage

这意味着 Content 不需要属性元素标记。 假定在开始和结束 ContentPage 标记之间显示的任何 XML 内容都将分配给 Content 属性。

StackLayoutGridAbsoluteLayoutRelativeLayout 都派生自 Layout<View>,如果在 Xamarin.Forms 文档中查找 Layout<T>,你将看到另一个 ContentProperty 特性:

[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout ...

这允许将布局的内容自动添加到 Children 集合中,而无需显式 Children 属性元素标记。

其他类还具有 ContentProperty 特性定义。 例如,Label 的内容属性为 Text。 查看其他 API 文档。

与 OnPlatform 的平台差异

在单页应用程序中,通常会设置页面上的 Padding 属性以避免覆盖 iOS 状态栏。 在代码中,可以使用 Device.RuntimePlatform 属性实现此目的:

if (Device.RuntimePlatform == Device.iOS)
{
    Padding = new Thickness(0, 20, 0, 0);
}

还可以使用 OnPlatformOn 类在 XAML 中执行类似操作。 首先包括页面顶部附近的 Padding 属性的属性元素:

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

    <ContentPage.Padding>

    </ContentPage.Padding>
    ...
</ContentPage>

在这些标记中,包括 OnPlatform 标记。 OnPlatform 是泛型类。 在这种情况下,需要指定泛型类型参数 Thickness,它是 Padding 属性的类型。 幸运的是,有一个 XAML 特性专门用于定义调用 x:TypeArguments 的泛型参数。 这应与要设置的属性的类型匹配:

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

        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

OnPlatform 具有名为 Platforms 的属性,该属性是 On 对象的 IList。 对该属性使用属性元素标记:

<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">
            <OnPlatform.Platforms>

            </OnPlatform.Platforms>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

现在添加 On 元素。 对于每个属性,将 Platform 属性和 Value 属性设置为 Thickness 属性的标记:

<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">
            <OnPlatform.Platforms>
                <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.Platforms>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

可以简化此标记。 OnPlatform 的内容属性是 Platforms,因此可以移除这些属性元素标记:

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

OnPlatform 属性属于类型 IList<string>,因此,如果值相同,则可以包含多个平台:

<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, UWP" Value="0, 0, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

由于 Android 和 UWP 设置为 Padding 的默认值,因此可以移除该标记:

<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" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

这是在 XAML 中设置依赖于平台的 Padding 属性的标准方法。 如果 Value 设置不能由单个字符串表示,则可以为其定义属性元素:

<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">
                <On.Value>
                    0, 20, 0, 0
                </On.Value>
            </On>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

注意

还可以在 XAML 中使用 OnPlatform 标记扩展来自定义每个平台的 UI 外观。 它提供的功能与 OnPlatformOn 类相同,但表现形式更简洁。 有关详细信息,请参阅 OnPlatform 标记扩展

总结

使用属性元素和附加属性,已经建立了大部分基本 XAML 语法。 但是,有时需要以间接方式将属性设置为对象,例如,从资源字典中设置属性。 下一部分(第 3 部分)会介绍此方法。XAML 标记扩展