关于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的层次结构
- 默认命名空间 (
xmlns="...") - WPF核心控件库 - x命名空间 (
xmlns:x="...") - XAML语言特性 - 自定义映射 (
xmlns:别名="clr-namespace:...") - 引用自己的代码 - 设计时支持 (
xmlns:d="...") - 设计器辅助功能 - 兼容性处理 (
xmlns:mc="...") - 版本和工具兼容
这种分层设计让XAML既能使用框架控件,又能扩展自定义功能,同时支持设计时和运行时的不同需求。