XAML语法基础

XAML(eXtensible Application Markup Language)是WPF用于描述界面的声明式标记语言,它基于XML语法,本质上是一种对象实例化和属性赋值的描述方式,我们在XAML中写的每一个元素、每一个特性,最终都会映射成.NET对象的创建和属性设置。

元素(Element)和特性(Attribute)

以下面按钮的XAML为例:

<Button Content="确定" Background="Red" Width="100" Height="30" />

在XML的官方定义和标准术语中,标签内的键值对如Content="确定"被称为特性(Attribute),标签本身如<Button>被称为元素(Element)

XAML与对象实例化和属性赋值

XAML并没有什么魔法,一份XAML和一段C#代码是可以严格等价的,比如下面这段XAML描述了一个红色背景、带文字的按钮。

<Button Content="确定" Background="Red" Width="100" Height="30" />

它与下面的C#代码完全等价,元素名对应类型,特性(Attribute)对应属性赋值。

var button = new Button
{
    Content = "确定",
    Background = Brushes.Red,
    Width = 100,
    Height = 30
};

这里注意Background="Red",XAML写的是一个字符串,而C#中是Brush对象,中间是靠WPF内置的类型转换器(TypeConverter)自动完成转换的,这使得XAML可以用非常简洁的字符串表达颜色、长度、枚举等复杂类型。

嵌套的元素对应对象之间的父子关系,下面这段XAML在窗口中放了一个StackPanelStackPanel里又放了两个控件。

<Window x:Class="Gacfox.Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Gacfox.Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <TextBlock Text="请输入姓名:" />
        <TextBox />
    </StackPanel>
</Window>

XAML命名空间

XAML本质还是XML,元素名前可以有命名空间前缀,前缀通过xmlns声明映射到一组.NET类型。上面例子中出现过以下命名空间声明:

默认命名空间xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation",它映射了System.WindowsSystem.Windows.Controls等WPF核心命名空间,所以ButtonTextBox这些元素名不需要加前缀就能直接使用。

XAML语言命名空间xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml",前缀x是约定俗成的,它映射的是XAML语言本身的指令,例如x:Classx:Name等,这些不是普通的.NET属性,而是交给XAML编译器特殊处理的。

如果想在XAML中使用自己写的类或第三方程序集中的类型,需要用clr-namespace语法引入CLR命名空间,例如xmlns:local="clr-namespace:Gacfox.Demo"把当前程序集的Gacfox.Demo命名空间映射为local前缀,之后就可以写<local:MyUserControl />这样的元素,如果类型在别的程序集中,还要追加程序集名,例如clr-namespace:Some.Namespace;assembly=SomeAssembly

属性赋值的两种方式

特性语法

特性语法就是我们前面一直在用的形式,把属性写成XML特性,值是字符串,由类型转换器解析。

<Button Content="确定" Background="Red" FontSize="16" />

特性语法简洁,但值只能是字符串,遇到属性值本身是复杂对象时就力不从心了。

属性元素语法

属性元素语法用父元素.属性名形式的子元素来为属性赋值,值可以是任意复杂的对象树,下面的例子为按钮的背景设置了一个线性渐变画刷。

<Button Content="确定" Width="120" Height="36">
    <Button.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
            <GradientStop Color="LightBlue" Offset="0" />
            <GradientStop Color="SteelBlue" Offset="1" />
        </LinearGradientBrush>
    </Button.Background>
</Button>

这里的Button.Background并不是一个新的控件,而是用于给ButtonBackground属性赋值的语法标记。特性语法和属性元素语法可以混用,但注意同一个属性只能用其中一种赋值一次。

内容属性语法

我们已经看到<Button Content="确定" />这样的写法,其实对于按钮还可以省略Content,直接把内容写在元素内部。

<Button Width="120" Height="36">确定</Button>

这是因为ButtonContent标记为了内容属性(ContentProperty),写在元素标签之间的内容会自动赋给内容属性。WPF中,每个类最多只能指定一个内容属性,StackPanelGrid这类面板的内容属性是Children,所以我们直接在标签里写子控件即可。

内容属性同样支持放复杂对象,按钮的内容甚至可以是图形加文字的组合。

<Button Width="120" Height="36">
    <StackPanel Orientation="Horizontal">
        <Ellipse Width="12" Height="12" Fill="Green" />
        <TextBlock Text="运行中" Margin="6,0,0,0" />
    </StackPanel>
</Button>

标记扩展语法

标记扩展(Markup Extension)是XAML中一种特殊的赋值语法,用一对花括号{}表示,它把属性值的求值交给一个扩展对象在运行时完成,而非简单的字面量转换。常见的标记扩展有如下几种。

<Window.Resources>
    <SolidColorBrush x:Key="MainBrush" Color="SteelBlue" />
</Window.Resources>
<StackPanel>
    <!-- 数据绑定:把Text绑定到数据上下文的Name属性 -->
    <TextBlock Text="{Binding Name}" />
    <!-- 引用静态资源:使用上面定义的画刷 -->
    <TextBlock Text="标题" Foreground="{StaticResource MainBrush}" />
    <!-- 显式赋空值 -->
    <Image Source="{x:Null}" />
</StackPanel>
  • {Binding ...}:数据绑定,WPF数据驱动的核心
  • {StaticResource Key}:引用资源字典中定义的资源
  • {x:Null}:显式把属性赋为null

有关标记扩展的更多细节我们到对应章节再深入讲解。

x:前缀常用指令

x:前缀的指令由XAML编译器处理,常用的有以下几个。

指令 说明
x:Class 指定XAML对应的代码隐藏类,只在根元素上使用
x:Name 给元素命名,编译后生成同名成员字段,可在代码中访问
x:Key 给资源字典中的条目指定键,配合{StaticResource}/{DynamicResource}使用
x:Type 传递一个类型本身(而非实例),常用于样式TargetType="{x:Type Button}"
x:Null 表示null

其中x:Name用得最多,它相当于Winform属性面板中的(Name),命名的元素可以在C#代码中直接用同名成员变量访问。

<TextBox x:Name="inputTextBox" />
string text = inputTextBox.Text;

事件在XAML中的挂接

XAML中可以直接用特性语法把事件挂接到代码隐藏类的方法上。

<Button Content="确定" Click="okButton_Click" />

对应的事件处理函数写在代码隐藏类中,签名为void 方法名(object sender, RoutedEventArgs e),其中不同事件的参数类型可能不同,不过大多数都是RoutedEventArgs及其派生类。

namespace Gacfox.Demo;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void okButton_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("点击了确定");
    }
}

事件挂接在编译期就会校验方法签名是否匹配,写错名字会直接编译失败,比运行时才报错友好得多。除了Click,控件的常用事件都可以这样挂接。WPF事件背后是一套比Winform更强大的路由事件机制,我们将在后续章节详细讨论。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。