(四)深入浅出 WPF——x 名称空间详解

251 阅读11分钟

x 名称空间里的成员(如 x:Class、x:Name)是专门写给 XAML 编译器看的、用来引导 XAML编译器把 XAML 代码编译成 CLR 代码的(所以这个 x 不是为了酷,而是 XAML 的首字母)。

但凡包含 XMAL 代码的 WPF 程序都需要通过语局 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"来引入http://schemas.microsoft.com/winfx/2006/xaml这个名称空间。

4.1 x 名称空间里都有什么

x 名称空间包含的类均与解析 XAML 语言相关,所以也可以称之为“XAML 名称空间”。

与 C# 语言一样,XAML 也有子级的编译器。XAML 语言会被解析并编译,最终形成微软中间语言存储在程序集中。在解析和编译 XAML 代码的过程中,我们经常需要高速编译器一些重要的信息,比如 XAML 代码的编译结果应该与哪个 C# 代码的编译结果合并、使用 XAML 声明的元素是 public 还是 private 访问级别等等。这些让程序员能够与 XAML 编译器沟通的工具就存放在 x 名称空间中。如下表所示。

名称种类(在 XAML 中出现的形式)
x:Array标签扩展
x:ClassAttribute
x:ClassModiferAttribute
x:CodeXAML 指令元素
x:FieldModiferAttribute
x:KeyAttribute
x:NameAttribute
x:Null标签扩展
x:SharedAttribute
x:Static标签扩展
x:SubclassAttribute
x:Type标签扩展
x:TypeArgumentsAttribute
x:UidAttribute
x:XDataXAML 指令元素

4.2 x 名称空间中的 Attribute

在使用 XAML 编程的时候,如果你想给它加上一些特殊的标记从而影响 XMAL 编译器对它的解析,这时候就需要额外为它添加一些 Attribute 了。比如你想告诉 XAML 编译器将编译结果与哪个 C# 编译器的类合并,这时候就必须为这个标签添加 x:Class="目标类名" 这样一个 Attribute 以告知 XAML 编译器。x:Class 这个 Attribute并不是对象的成员,而是我们把它从 x 名称空间拿出来硬贴上去的。

4.2.1 x:Class

这个 Attribute的作用是告诉 XAML 编译器将 XAML 标签的编译结果与后台代码中制定的类合并。在使用 x:Class 时必须遵循以下要求:

  • 这个 Attribute 只能用于根节点。
  • 使用 x:Class 的根节点的类型要与 x:Class 的值所指示的类型保持一致。
  • x:Class 的值所指示的类型在声明时必须使用 partial 关键字。

4.2.2 x:ClassModifer

这个 Attribute 的作用是告诉 XAML 编译由标签编译生成的类具有怎样的访问控制级别。

使用这个 Attribute 时需要注意:

  • 标签必须具有 x:Class Attribute。
  • x:ClassModifer 的值必须与 x:Class 所指示类的访问控制级别一致。
  • x:ClassModifer 的值随后台代码的编译语言不同而有所不同,参见 TypeAttribute 枚举类型。

4.2.3 x:Name

XAML 的标签声明的是对象,一个 XAML 标签会对应着一个对象。在 .NET 平台上,类是引用类型。引用类型的实例在使用时一般是以“引用者->实例”的形式出现的。

常见的引用者是引用变量,但这并不是唯一的。比如下面这段 XAML 代码:

<Window x:Class="XNameSpace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <TextBox Margin="5"/>
        <Button Content="OK" Margin="5" Click="ButtonBase_OnClick"></Button>
    </StackPanel>
</Window>

这段代码中通篇没有出现一个名字,但我们却可以通过引用者的层级关系来找到我们最终想要的控件。下面是 Button 的 Click 事件处理器:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    StackPanel stackPanel = this.Content as StackPanel;
    TextBox textBox = stackPanel.Children[0] as TextBox;
    if (string.IsNullOrEmpty(textBox.Name))
    {
        textBox.Text = "No Name!";
    }
    else
    {
        textBox.Text = textBox.Name;
    }
}

Window1.Content 属性引用着 StackPanel 的实例,而 StackPanel 实例的 Children[0] 又引用着 TextBox 的实例。

虽然理论上我们可以通过这种方法访问到 UI 上的所有元素,但这毕竟太繁琐了。换句话说: XAML 这种对象声明语言只负责声明对象而不负责为这些对象声明引用变量。如果我们需要为对象准备一个引用变量以便在 C# 代码中直接访问就必须显示地告诉 XAML 编译器——为这个对象声明引用变量,这时 x:Name 就派上用场了。

x:Name 的作用有两个:

  • 告诉 XAML 编译器,当一个标签带有 x:Name 时除了为这个标签生成对应的实例外还要为这个实例声明一个引用变量,变量名就是 x:Name 的值。
  • 将 XAML 标签对应的 Name 属性(如果有)也设为 x:Name 的值,并把这个值注册到 UI 树上,以方便查找。

经常有初学者问:在 XAML 代码中应该使用 Name 呢,还是 x:Name? Name 属性定义在 FrameworkElement 类中,这个类是 WPF 控件的基类,所以所有 WPF 控件都具有 Name 这个属性。当一个元素具有 Name 属性时,你使用 Name 或 x:Name 效果是一样的。对于那些没有 Name 属性的元素,为了在 XAML 声明时也创建引用变量以便在 c# 代码中访问,我们就只能使用 x:Name。因此 x:Name 的功能涵盖了 Name 属性的功能,所以全部使用 x:Name 以增强代码的统一性和可读性。

4.2.4 x:FieldModifier

使用 x:Name 后,XAML 标签对应的实例就具有了自己的引用变量,而且这些引用变量都是类的字段。既然是类的字段就面部俩关注一下它们的访问级别。默认情况下,这些字段的访问级别按照面向对象的封装原则被设置成了 internal。有时候我们需要从一个程序集访问另一个程序集中窗体的元素,这时候就需要把被访问控件的引用变量改为 public级别,x:FieldModifer 就是用来在 XAML 里改变引用变量访问级别的。

<StackPanel>
    <TextBox x:Name="TextBox1" x:FieldModifier="public" Margin="5"/>
    <TextBox x:Name="TextBox2" x:FieldModifier="public" Margin="5"/>
    <TextBox x:Name="TextBox3" x:FieldModifier="public" Margin="5"/>
</StackPanel>

4.2.5 x:Key

最自然的检索方式莫过于使用“Key-Value”对的形式了。在 XAML 文件中,我们可以把很多需要多次使用的内容提取出来放在资源字典(Resource Dictionary)里。

x:Key 的作用就是为资源贴上用于检索的索引。在 WPF 中,几乎每个元素都有自己的 Resources 属性,这个属性是个“Key-Value”集合,只要把元素放进这个集合,这个元素就成为资源字典中的一个条目。资源(Resources)在 WPF 非常重要,需要重复使用的 XAML 内容,如 Style、各种 Template 和动画等都需要放在资源里。

在这里,我们使用一个例子简单地说明 x:Key 的用法。我们现在 Window1 的字典里添加一个条目,这个条目是一个字符串。

先让我们看 XAML 代码:

<Window x:Class="XNameSpace.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:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:XNameSpace"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <sys:String x:Key="MyString">Hello WPF Resource!</sys:String>
    </Window.Resources>
    <StackPanel Background="Gray">
        <TextBox Text="{StaticResource ResourceKey=MyString}" Margin="5"/>
        <TextBox x:Name="TextBox" Margin="5"/>
        <Button Content="Show" Click="ButtonBase_OnClick"></Button>
    </StackPanel>
</Window>

为了在 XAML 中使用 String 类,需要用 xmlns:sys="clr-namespace:System;assembly=mscorlib" 引用 mscorlib.dll。

资源不但可以在 XAML 中访问,在 c# 中也可以访问。下面是 Button.Click 的事件处理器:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    string str = this.FindResource("MyString") as string;
    this.TextBox.Text = str;
}

4.2.6 x:Shared

在学习 x:Key 时我们已经知道,一旦我们把某些对象当做资源放进资源字典里后就可以把它们检索出来重复使用。那么,每当它们检索到一个对象时,我们得到的究竟是同一个对象呢,还是这个对象的多个副本呢?这就要看我们给 x:Shared 赋什么值了。x:Shared 一定要与 x:Key配合使用,如果 x:Shared 的值为 true,那么我们每次检索到这个对象时,我们得到的都是同一个对象,否则,如果值为 false,每次得到的都是这个对象的一个新副本。x:Shared 的值默认是 true。

4.3 x 名称空间中的扩展标记

从前面的章节我们知道,标记扩展(Markup Extension)实际上就是一些 MarkupExtension 类的直接或者间接派生类。x 名称空间中就包含一些这样的类,所以常称它们为 x 名称空间的标记扩展。

4.3.1 x:Type

为了能让程序员在编程层面上自由地操作这些数据类型,比如在不知道具体数据类型的情况下创建这个类型的实例并尝试调用它的方法,.NET Framework 中还包含了名为 Type 的类作为所有数据类型在编程层面的抽象。

请看下面的例子,首先,我们创建了一个 Button 派生类。

public class MyButton: Button
{
    public Type UserWindowType { get; set; }

    protected override void OnClick()
    {
        base.OnClick();
        Window win = Activator.CreateInstance(UserWindowType) as Window;
        if (win != null)
        {
            win.ShowDialog();
        }
    }
}

然后,在项目里添加了一个名为 MyWindow 的 Window派生类:

<Window x:Class="XNameSpace.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:XNameSpace"
        mc:Ignorable="d"
        Title="MyWindow" Height="170" Width="200">
    <StackPanel Background="LightBlue">
        <TextBox Margin="5" />
        <TextBox Margin="5" />
        <TextBox Margin="5" />
        <Button Content="OK" Margin="5" />
    </StackPanel>
</Window>

最后,把自定义按钮添加到主窗口的界面上,并把 MyWindow 作为一种数据类型赋值给 MyButton.UserWindowType 属性:

<Window x:Class="XNameSpace.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:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:XNameSpace"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <local:MyButton Content="Show" UserWindowType="{x:Type TypeName=local:MyWindow }"></local:MyButton>
    </StackPanel>
</Window>

注意:因为 MyButton 和 MyWindow 这两个自定义类都包含在当前项目的名称空间里,所以把当前项目的名称空间引用进来并用local前缀映射:xmlns:local="clr-namespace:WpfApplication1"。在使用 MyButton 和 MyWindow 时也要为它们加上 local 前缀。

4.3.2 x:Null

有时我们需要显式地对一个属性赋一个空值。在 XAML 里用 x:Null 表示空值。

<Window x:Class="XNameSpace.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:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:XNameSpace"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
            <Setter Property="Width" Value="60" />
            <Setter Property="Height" Value="36" />
            <Setter Property="Margin" Value="5" />
        </Style>
    </Window.Resources>
    <StackPanel>
        <Button Content="OK" />
        <Button Content="OK" />
        <Button Content="OK" />
        <Button Content="OK" Style="{x:Null}" />
    </StackPanel>
</Window>

上面的例子把一个 Style 放在了 Window 的资源里并把它的 x:Key 和 TargetType 都设置成了 Button 类型,这样,UI 上的所有 Button 都会默认地被套用这个 Style——除了最后一个 Button——因为它显式地把 Style 设置为了 x:Null。

4.3.3 标记扩展实例的两种声明语法

前面我们你已经认识了 x:Type 和 x:Null 两种标记扩展,并使用了它们的转义字符串式声明。因为标记扩展也是标准的 .NET 类,所以,我们也可以使用 XAML 标签来声明标记扩展的实力。拿上面的 x:Null 的例子来说,最后一个 Button 的代码完全可以写成这样:

<Button Content="OK">
    <Button.Style>
        <x:Null/>
    </Button.Style>
</Button>

这样做的缺点是代码太啰嗦,所以我们很少使用这种语法。但有一个例外,就是 x:Array 标记扩展。

4.3.4 x:Array

x:Array 的作用就是通过它的 Items 属性向使用者暴露一个类型已知的 ArrayList 实例,ArrayList 内成员的类型由 x:Array 的 Type 指明。下面这个例子是把一个 x:Array 当作数据源向一个 ListBox 提供数据。在 WPF 中把包含数据的对象称为数据源(Data Resource)。

<Grid Background="LightBlue">
    <ListBox Margin="5" ItemsSource="{x:Array Type=sys:String}"/>
</Grid>

我们需要为 x:Array 实例添加一些数据,使用标签声明语法:

<Grid Background="LightBlue">
    <ListBox Margin="5">
        <ListBox.ItemsSource>
            <x:Array Type="sys:String">
                <sys:String>Tim</sys:String>
                <sys:String>Tom</sys:String>
                <sys:String>Victor</sys:String>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>
</Grid>

4.3.5 s:Static

x:Static 是一个很常用的标记扩展,它的功能是在 XAML 文档中使用数据类型的 static 成员,因为 XAML 不能编写逻辑代码,所以使用 x:Static 访问的 static 成员一定是数据类型的属性或字段。我们看一个例子:

首先,为 Window1 添加两个 static 成员,一个是 static 字段,一个是 static 属性:

public partial class MainWindow : Window
{
    public static string WindowTitle = "XNameSpace";
    public static string ShowText { get { return WindowTitle + " - Show Window"; } }
}
<Window x:Class="XNameSpace.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:XNameSpace"
        mc:Ignorable="d"
        Title="MyWindow" Height="170" Width="200">
    <StackPanel>
        <TextBlock FontSize="32" Text="{x:Static local:MainWindow.WindowTitle}" />
    </StackPanel>
</Window>

注意:如果一个程序需要国际化支持,一般会把需要显式的字符串保存在一个资源类的 static 属性中,所以支持国际化的程序 UI 中对 x:Static 的使用非常频繁。

4.4 XAML 指令元素

XAML 指令元素只有两个

  • x:Code
  • x:XData

x:Code 的作用就是可以包含一些本应该放在后置代码中的 c# 代码。

x:XData 标签是一个专用标签。WPF 中把包含数据的对象称为数据源,用于把数据源中的数据提供给数据使用者的对象称为数据提供者(Data Provider)。WPF 类库只拿过包含多种数据提供者。其中一个类叫做 XmlDataProvider,专门用于提供 XML 化的数据。如果想在 XAML 里声明一个具有数据的 XmlDataProvider 实例,那么 XmlDataProvider 实例的数据就要放在 x:Data 标签的内容里。实例如下:

<Window.Resources>
    <XmlDataProvider x:Key="InventoryData" XPath="Inventory/Books">
        <x:XData>
            <SuperMarket xmlns="">
                <Fruits>
                    <Fruit Name="Peach"/>
                    <Fruit Name="Banana"/>
                    <Fruit Name="Orange"/>
                </Fruits>
                <Drinks>
                    <Drink Name="Coca"/>
                    <Drink Name="Pesi"/>
                </Drinks>
            </SuperMarket>
        </x:XData>
    </XmlDataProvider>
</Window.Resources>

4.5 小结

至此,我们可以说已经比较完整地掌握了 XAML 的语法和常用元素。