Microsoft 将 XAML 定义为 "简单"、"通用"、"声明式" 的 "编程语言"。这意味着我们会在更多的地方看到它(比如 Silverlight),而且它显然比其原始版本 XML (XAML 是一种基于 XML 且遵循 XML 结构规则的语言) 多了更多的逻辑处理手段。如果愿意的话,我们完全可以抛开 XAML 来编写 WPF 程序。只不过这有点类似用记事本开发 .NET 程序的意味,好玩不好用。XAML 的定义模式使得非编程人员可以用 "易懂" 的方式来刻画 UI,并且这种方式我们早已熟悉,比如 WebForm,亦或者是我一直念念不忘的 Delphi Form (偶尔想起而已,其实早将 Object Pascal 忘得精光了)。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
</Grid>
</Window>
这是一个非常简单的 XAML,它定义了一个空白 WPF 窗体(Window)。XAML 对应于 .NET 代码,只不过这个过程由特定的 XAML 编译器和运行时解释器完成。当解释器处理上面这段代码时,相当于:
从这里我们可以体会两者的区别,用 XAML 的好处是可以在设计阶段就能看到最终的展现效果,很显然这是美工所需要的。你可以从 VS 命令行输入 "xamlpad.exe",这样你会看到直观的效果。
作为一种应用于 .NET 平台的 "语言",XAML 同样支持很多我们所熟悉也是必须的概念。
1. Namespace
XAML 默认将下列 .NET Namespace 映射到 "http://schemas.microsoft.com/winfx/2006/xaml/presentation":
System.Windows
System.Windows.Automation
System.Windows.Controls
System.Windows.Controls.Primitives
System.Windows.Data
System.Windows.Documents
System.Windows.Forms.Integration
System.Windows.Ink
System.Windows.Input
System.Windows.Media
System.Windows.Media.Animation
System.Windows.Media.Effects
System.Windows.Media.Imaging
System.Windows.Media.Media3D
System.Windows.Media.TextFormatting
System.Windows.Navigation
System.Windows.Shapes
除了这个包含绝大多数 WPF 所需类型的主要命名空间外,还有一个是 XAML 专用的命名空间 (System.Windows.Markup) —— "http://schemas.microsoft.com/winfx/2006/xaml"。使用非默认命名空间的语法有点类似于 C# Namespace Alias, 我们需要添加一个前缀,比如下面示例中的 "x"。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<TextBox x:Name="txtUsername" Background="{x:Null}"></TextBox>
</Grid>
</Window>
我们还可以引入 CLR Namespace。
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Int32 x:Key="key1">1</sys:Int32>
</collections:Hashtable>
2. Property
我们可以用下面两种方式来设置 XAML 元素的属性。
方式1
方式2
<Label.Content>
Label11
</Label.Content>
<Label.Foreground>
Blue
</Label.Foreground>
</Label>
WPF 会按下列顺序将 XAML 中的属性字符串转换为实际属性值。
(1) 属性值以大括号开始,或者属性是从 MarkupExtension 派生的元素,则使用标记扩展处理。
(2) 属性用指定的 TypeConverter 声明的,或者使用了转换特性(TypeConverterAttribute),则提交到类型转换器。
(3) 尝试基元类型转换,包括枚举名称检查。
<Setter ... />
</Trigger>
3. TypeConverter
WPF 提供了大量的类型转换器,以便将类似下面示例中的 Red 字符串转换城 SystemWindows.Media.Brushes.Red。
等价于
不过下面的代码更能反应运行期的实际转换行为
this.label10.Foreground = (Brush)typeConverter.ConvertFromInvariantString("Red");
有关转换器列表,可参考:ms-help://MS.MSDNQTR.v90.chs/fxref_system/html/35bffd5f-b9aa-1ccd-99fe-b0833551e562.htm
4. MarkupExtension
对 XAML 的一种扩展,以便支持复杂的属性值。这些标记扩展通常继承自 MarkupExtension,并使用大括号包含。WPF 提供了一些常用的标记扩展,诸如 NullExtension、StaticExtension、DynamicResourceExtension、StaticResourceExtension、Binding 等。和 Attribute 规则类似,我们通常可以省略 Extension 这个后缀。需要注意的是某些标记扩展属于 System.Windows.Markup,因此我们需要添加命名空间前缀。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<TextBox Background="{x:Null}"></TextBox>
</Grid>
</Window>
我们可以为标记扩展提供其所需的构造参数。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<Label Content="{x:Static SystemParameters.IconHeight}" />
</Grid>
</Window>
这个例子中,我们将 System.Windows.SystemParameters.IconHeight 值作为参数传递给 "public StaticExtension(string member)" 构造方法,这种参数通常被称作定位参数。而另外一种参数是将特定的值传给标记扩展对象属性,语法上必须指定属性名称,故被称之为命名参数。下面的例子表示将 textBox1.Text 参数绑定到 Label.Content 上,这样当编辑框内容发生变化时,标签内容自动保持同步。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<TextBox Name="textBox1" />
<Label Content="{Binding ElementName=textBox1, Path=Text}" />
</Grid>
</Window>
标记扩展允许嵌套,并可以引用自身。我们看另外一个例子。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<TextBox Name="textBox2" Width="128" Text="{Binding RelativeSource={RelativeSource Self}, Path=Width}" />
</Grid>
</Window>
这个例子的意思是将 TextBox.Text 内容绑定为其自身(Self)的高度值(Width)。
标记扩展带来一个问题就是大括号的转义,毕竟很多时候我们需要在内容显示中使用它。解决方法是在前面添加一对额外的大括号。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<Label Content="{}{Hello, World!}" />
</Grid>
</Window>
如果觉得难看,也可以写成下面这样。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<Label>{Hello, World!}</Label>
</Grid>
</Window>
5. Content
XAML 这点和 HTML 非常类似,我们可以将任何内容添加到元素内容项中,这带来更加丰富的 UI 表达能力,再也不像 WinForm 那样 "能做什么,不能做什么"。
<Hyperlink>Click</Hyperlink>
</Button>
有一点需要注意,内容项并不一定就是 Content。像 ComboBox、ListBox、TabControl 使用 Items 作为内容项。
6. XamlReader & XamlWriter
通常情况下,XAML 在项目编译时会被压缩成 BAML (Binary Application Markup Language) 保存到资源文件中。BAML 只是包含 XAML 的纯格式声明,并没有任何事件之类的执行代码,切记不要和 MSIL 相混淆。XAML 运行期解释器解读 BAML 并生成相应的元素对象。
System.Windows.Markup 命名空间中提供了 XamlReader、XamlWriter 两个类型,允许我们手工操控 XAML 文件。
window.ShowDialog();
当然,我们还可以从文件流中读取。
{
var window = (Window)XamlReader.Load(stream);
var button = (Button)window.FindName("btnOK");
button.Click += (s, ex) => MessageBox.Show("Hello, World!");
window.ShowDialog();
}
test.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<Grid>
<Button x:Name="btnOK">OK</Button>
</Grid>
</Window>
需要注意的是 XamlReader 载入的 XAML 代码不能包含任何类型(x:Class)以及事件代码(x:Code)。
我们可以用 XamlWriter 将一个编译的 BAML 还原成 XAML。
MessageBox.Show(xaml);
输出:
Title="Window2" Width="300" Height="300"
xmlns="clr-namespace:Learn.WPF;assembly=Learn.WPF"
xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<av:Grid>
<av:Button Name="btnOK">OK</av:Button>
</av:Grid>
</Window2>
XAML 的动态载入在使用动态皮肤场景时非常有用,现在只要了解一下即可。