关于WPF中xmlns的理解

命名空间映射

以一个通过Visual Studio直接创建的简单WPF项目为例,我们查看其MainWindow.xaml文件,这是一个纯粹的窗口:

<Window x:Class="Test.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:Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
            
    </Grid>
</Window>

其中这一部分很难理解:

x:Class="Test.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:Test"
mc:Ignorable="d"

微软官方文档说,这个部分的内容其实叫XAML命名空间映射。

命名空间重命名

我们知道,在C#中就存在命名空间,也就是我们的namespace。

而XAML则是微软基于XML开发的一种声明性语言,WPF是XAML最出名的使用者,而关于XAML的解析则由.NET框架实现,它常被用于定义构造用户界面(UI)。

显然这是两种不同的语言,但显然两者需要进行交互,那么XAML就需要一种功能来调用命名空间,而这就是xmlns(XML/XAML namespace)的功能。

我们知道,对于C#中,调用其它命名空间的一种方式就是使用using语句,比如:

using System.Collections.Generic;
List<string> list = new List<string>();

xmlns也是类似这样的功能,我们可以通过类似于以下的方式创建一个我们自定义于命名空间叫Test下的一个TestButton控件:

xmlns:ts="clr-namespace:Test"
<ts:TestButton />

当然,如果更加准确一点形容,它在C#中应该更加类似于以下这种形式:

using ts = Test;
ts.TestButton = new ts.TestButton();

也就是说,xmlns:(name)这种格式有点类似于引用命名空间并重命名或重映射,这里就是把Test这个命名空间重映射为ts了。

但是和C#中非常大的不同是,C#可以不重映射,比如直接通过以下形式实现:

Test.TestButton = new Test.TestButton;

但是XAML中要引用命名空间则必须先使用xmlns语句将其重映射一遍,其中XAML中调用命名空间时使用的clr-namespace:是一个固定关键词,在这个关键词后面可以通过类似C#的方式来写命名空间,比如如果TestButton也是一个命名空间的话,那么就可以通过加.的方式进行连接:

xmlns:tb="clr-namespace:Test.TestButton"

这一点用C#的思想去理解就可以了。

而事实上,我们的xmlns:local就是这个功能,它提供的服务就是将项目的命名空间引用起来了,而这个local可以改成任意其他名称,不过通常我们不用改。

默认命名空间

现在我们大概理解了xmlns的作用,那么让我们重新回顾语句,会发现其中有一个没加:的语句,即:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

这个语句其实代表了一个默认命名空间,而它后面跟的不是clr-namespace:而是一个网址。

事实上这个网址并不是一个网页,而是统一资源标识符(URI),使用这种格式而非命名空间名字,最大的原因是为了保证全局唯一性,因为microsoft.com这个网址是微软拥有的,所以这个URI只有微软能用,这个URI的信息主要如下:

http://schemas.microsoft.com/winfx/2006/xaml/presentation
                |           |        |      |      |
                公司        规范     年份  技术   具体组件

其中schemas代表模式或规范,而WinFx/2006就是WPF的代号和规范的年份,xaml代表用于XAML技术,presentation代表WPF的Presentation层。

补充一个使用技巧:当你想知道某个命名空间URI对应的实际程序集时,可以查看项目的*.csproj文件,里面会有相应的引用。比如默认WPF命名空间对应多个程序集引用。

简单理解,这一个自动生成的xmlns代表的就是WPF的整个库,而它没有:则是因为它将作为默认空间使用,在XAML中任何没有加前缀的控件,都会优先在这个命名空间里面寻找,比如:

<Button Content="Button" HorizontalAlignment="Center" VerticalAlignment="Center"/>

和我们前面的TestButton的调用方法不同,前面并没有加任何name:,所以这种Button就会直接在xmlns指向的默认命名空间中寻找。

否则,我们在前面加一个ts:

<ts:Button Content="Button" HorizontalAlignment="Center" VerticalAlignment="Center"/>

只是加了一个ts:,现在就必须在一个xmlns:ts的命名空间里面寻找了,如果前面没有引用这个空间,或者空间里面没有Button这个类,那么就会报错。

特殊的x

我们发现在我们开始举例的XAML代码里面,最开始有一个x:Class,而后面才出现了xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

并且通过我们刚才对URI的理解,我们发现这个x的URI和刚刚的xmlns的很相似,只是它的URI少了/presentation,这代表它似乎是直接对XAML本身的规范。

事实上在XAML中x也确实是一个比较特殊的命名空间,它提供的不是某些控件,而是XAML本身的一些语言特性或特殊操作。

x:Class就是这个命名空间中一个比较重要的操作,它提供了XAML和C#后台代码的连接,比如在XAML中我们看到:

x:Class="Test.MainWindow"

这其实对应了C#后台代码的:

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

可以发现,它们的命名空间和类名完美对应上了,在编译时,两个板块才能够连接在一起。

而它的原理就是指定了这个XAML编译后生成的局部类(partial class)应该与哪个C#类合并,这也解释了为什么C#中的类是partial的。

显然x在工作中并不是调用某个C#的类或方法,而是作为一种特殊工具来操作XAML本身。

x的常用功能很多,比如我们在XAML中通过语句添加了一个Button控件,就像我们刚才那样:

<Button Content="Button" HorizontalAlignment="Center" VerticalAlignment="Center"/>

我们或许可以在VS的布局界面看到这个按钮,但是我们该如何在C#中调用这个按钮呢,其实要做的就是添加x:Name,比如:

<Button x:Name="button" Content="Button" HorizontalAlignment="Center" VerticalAlignment="Center"/>

现在,我们就可以在C#后台代码中通过变量名button直接访问控制这个控件了,操作变得非常简单。

关于x,常用的指令有:

  • x:Class - 连接XAML和C#后台代码
  • x:Name - 给控件起名字,用于后台访问
  • x:Key - 给资源(样式、模板等)起标识符
  • x:Type - 表示一个类型
  • x:Static - 引用静态成员

另外x这个名字是可以改的,因为它只是一个约定俗成的名字,其本身仍然遵守XAML的语法规则。但是通常不建议修改,因为一些库里面习惯了这个约定俗成的名字,改了很可能会出现一些意想不到的错误,而且也会降低代码可读性。

用于设计的d

我们还可以看到一个自动生成的命名空间:

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

d=Design,代表设计时。

它和x类似,不直接提供控件但是提供一些特殊服务,而这里的服务则是一些供Visual Studio设计器或者Blend中显示,而运行时会消失的特殊属性,比如:

<Window
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    d:DesignHeight="450"
    d:DesignWidth="800">

这是在设计器里面提供了一个预览尺寸,方便在设计界面的时候看到效果,但是实际运行时窗口的大小则和这两个属性无关。

在实际项目开发中,特别是使用MVVM模式时,d:命名空间提供的设计时数据功能非常有用。比如:

d:DataContext="{d:DesignInstance Type=local:MainViewModel}"

这可以在设计器里显示绑定数据的效果,避免"设计时一片空白"的问题。所以对于专业WPF开发,d:是很有价值的工具。

不过你如果担心它会污染你的代码,出现一些意想不到的错误,其实可以放心,因为下面一个命名空间的功能解决了这个问题。

标记兼容性的mc

继续往下看,我们可以发现两个语句:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"

这里面都出现了mc命名空间,mc=Markup Compatibility,即标记兼容性。

这个命名空间通常与d:配合使用,它会告诉编译器,有些标记只是设计时使用的,运行的时候可以忽略,即mc:Ignorable="d"

如果没有这个声明,编译器在看到d:DesignHeight时可能会产生困惑,这个属性是什么,自己似乎并未见过(因为这个功能只在设计器或Blend中被调用实现了)。

此外,利用mc的一些特性,可以将某些实验功能或未来版本的功能标记为可忽略,这样旧版本的编译器就不会报错。

同样,大部分时候我们不需要关注这个标记的用法,需要用的时候再自行查询即可。

总结:xmlns的层次结构

  1. 默认命名空间 (xmlns="...") - WPF核心控件库
  2. x命名空间 (xmlns:x="...") - XAML语言特性
  3. 自定义映射 (xmlns:别名="clr-namespace:...") - 引用自己的代码
  4. 设计时支持 (xmlns:d="...") - 设计器辅助功能
  5. 兼容性处理 (xmlns:mc="...") - 版本和工具兼容

这种分层设计让XAML既能使用框架控件,又能扩展自定义功能,同时支持设计时和运行时的不同需求。