用ASP.NET Core编写Web应用程序时,除了需要知道C#之外,还需要了解HTML、CSS和JavaScript。创建Windows应用程序时,除了C#之外,还需要了解XAML。XAML不仅用于创建Windows应用程序,还用于Windows Presentation Foundation(WPF)、Windows WorkFlow Foundation(WCF)和Xamarin的跨平台应用程序。
可以用XAML完成的工作都可以用C#实现,每个XAML元素都用一个类表示,因此可以从C#中访问。那么,为什么还需要XAML?XAML通常用于描述对象及其属性,可以描述很深的层析结构。例如,Page包含一个Grid控件,Grid控件包含一个StackPanel和其他控件,StackPanel包含按钮和文本框控件。XAML便于描述这种层析结构,并通过XML特性或元素分配对象的属性。
XAML允许以声明的方式编写代码,而C#主要是一种命令式编程语言。XAML支持声明式定义。在命令式编程语言(如C#)中,用C#代码定义一个for循环,编译器就使用中间语言(IL)代码创建一个for循环。在声明性编程语言中,声明应该做什么,而不是如何完成。
注意:
虽然C#不是纯粹的命令式编程语言,但使用LINQ时,也是在以声明方式编写语法。Entity Framework Core(EF Core)的LINQ提供程序将LINQ查询转换为SQL语句。
XAML是一个XML语法,但它定义了XML的几个增强特性。XAML仍然是有效的XML。但是一些增强特性有特殊的意义和特殊的功能,例如,在XML特性中使用花括号,对于XML,这仍然只是一个字符串,因此是有效的XML。对于XAML,这是一个标记扩展。
在有效使用XAML之前,需要了解这门语言的一些重要特性。本章介绍了如下XAML特性:
- 依赖属性:从外部看起来,依赖属性像正常属性。然而,它们需要更少的存储空间,实现了变更通知。
- 路由事件:从外部看起来,路由事件像正常的.NET事件。然而,通过添加和删除访问器来使用自定义事件实现方式,就允许冒泡和隧道。事件从外部控件进入内部控件称为隧道,从内部控件进入外部控件称为冒泡。
- 附加属性:通过附加属性,可以给其他控件添加属性。例如,按钮控件没有属性用于把它自己定位在Grid控件的特性行和列上。在XAML中,看起来有这样一个属性。
- 标记扩展:编写XML特性需要的编码比编写XML元素少。然而,XML特性只能是字符串;使用XML元素可以编写更强大的语法。为了减少需要编写的代码量,标记扩展允许在特性中编写强大的语法。
1. XAML标准
WPF、UWP和Xamarin对XAML元素使用(部分仍然使用)不同的语法。例如,对于WPF和UWP,按钮有Content属性,而Xamarin的按钮有Text属性。在WPF和UWP中,可以使用StackPanle来排列多个元素。在Xamarin中,类似的控件是StackLayout。
为了更容易地在不同的UI技术堆栈之间切换,定义了XAML标准。有关标准的实际状态,请参见 https://github.com/Microsoft/xaml-standard/ 。
2. 将元素映射到类
在每个XAML元素的后面都有一个具有属性、方法和事件爱你的类。如前所述,可以使用C#代码或使用XAML创建UI元素。下面看一个例子。使用以下代码片段,定义了一个包含按钮控件的StakcPanel。使用XAML特性,按钮分配了Content属性和Click事件。Content属性只包含一个简单的字符串,而Click事件引用了方法OnButtonClick的地址。XML特性x:Name用于向按钮控件声明一个名词,该名称可以在XAML和C#代码隐藏文件中使用:
<StackPanel x:Name="stackPanel1">
<Grid>
<Button x:Name="button1" Content="Click Me!" Click="OnButtonClick"/>
</Grid>
</StackPanel>
在页面顶部,可以看到带有XML特性x:Class的Page袁术。这定义了类的名称,在该类中,XAML编译器生成了部分代码。使用Visual Studio中的代码隐藏文件,可以看到这个类中能修改的部分:
<Page
x:Class="XAMLIntro.MainPage"
...
</Page>
代码隐藏文件包含类MainPage的一部分(XAML编译器没有生成这个部分)。在构造函数中,调用方法InitializeComponent。InitializeComponet的实现是由XAML编译器创建的。该方法加载XAML文件,并将其转换为XAML文件中的根元素指定的对象。OnButtonClick方法是之前在XAML代码中创建的按钮的Click事件处理程序。这个实现打开了一个MessageDialog:
public MainPage()
{
this.InitializeComponent();
}
private async void OnButtonClick(object sender, RoutedEventArgs e)
{
await new MessageDialog("button 1 clicked").ShowAsync();
}
现在,在C#代码的Button类中创建一个新对象,并将其添加到现有的StackPanel中。在下面的代码片段中,修改了MainPage的构造函数,以创建一个新按钮,设置Content属性,并为Click事件分配一个Lambda表达式。最后,新创建的按钮添加到StackPanel的Children中:
public MainPage()
{
this.InitializeComponent();
var button2 = new Button
{
Content = "created dynamically"
};
button2.Click += async (sender,e) =>
{
await new MessageDialog("button 2 clicked").ShowAsync();
};
stackPanel1.Children.Add(button2);
}
如前所述,XAML只是处理对象、属性和事件的另一种方式。下一节将展示XAML在用户界面上的优势。
3. 通过XAML使用定制的.NET类
要在XAML代码中使用自定义的.NET类,可以使用简单的POCO类,对类定义没有特殊要求。只需要将.NET名称空间添加到XAML声明中。为了演示这一点,下面设计一个具有FirstName和LastName属性的简单Person类:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString() =>
$"{FirstName} {LastName}";
}
在XAML中定义了一个名为datalib的XML的名称空间别名,它映射到程序集DataLib中的.NET名称空间DataLib。有了这个别名,现在就可以把别名作为元素前缀,来使用这个名称空间中的所有类。
在XAML代码中添加一个列表框,其中包含Person类型的项。使用XAML特性,可以设置属性FirstName和LastName的值。运行应用程序时,ToString()方法的输出显示在列表框中:
<Page
x:Class="XAMLIntro.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:XAMLIntro"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:datalib="using:DataLib"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel x:Name="stackPanel1">