- A+
XAML使用标签来定义Ul元素(UIElement),每个标签对应NET Framework类库中的一个控件类。通过设置标签的Atribute,不但可以对标签所对应控件对象的Property进行赋值,还可以做一些额外的事件(如声明命名空间、指定类名等)。
树形结构
在界面上添加一些控件,界面如下:
界面的XAML如下所示:
<Window x:Class="LearnWpf.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:LearnWpf" mc:Ignorable="d" Title="MainWindow" Height="164.458" Width="344.578"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="69*"/> <ColumnDefinition Width="19*"/> </Grid.ColumnDefinitions> <TextBox HorizontalAlignment="Left" Height="23" Margin="10,33,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="245"/> <Button Content="Button" HorizontalAlignment="Left" Margin="10,91,0,0" VerticalAlignment="Top" Width="245" Height="21"/> <StackPanel HorizontalAlignment="Left" Height="25" Margin="10,61,0,0" VerticalAlignment="Top" Width="245" Orientation="Horizontal"> <TextBox HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/> <TextBox HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/> </StackPanel> <TextBox HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="245" /> </Grid> </Window>
可以看出UI是平面结构,XAML是树形结构,而且同一种UI布局界面的XAML代码可以有多种实现。在开发时,可以通过WPF基本类库中的VisuaTreeHelper和LogicarTreeHelper两个助手类操作XAML树。
VS2019可以通过“视图->其他窗口->文档大纲”查看XAML代码的结构,如图所示:
对象属性赋值语法
XAML是一种声明性语言,在文档只能使用标签声明对象、初始化对象的属性。XAML中为对象属性赋值共有两种语法:
- 使用字符串(标签的Attribute)进行简单赋值
- 使用属性元素(Property Element)进行复杂赋值。
注:能使用Attribute=Value形式赋值的就不使用属性元素。
使用标签的Attribute
一个标签的Attribute里有一部分与对象的Property互相对应,如<Rectangle>标签的Fill这个Attribute与Rectangle类对象的Fill属性对应。Rectangle.Fill的类型Brush是一个抽象类,凡是以Brush为基类的类都可作为Fill属性的值。
使用字符串对Atribute进行简单赋值,让Rectangle填充成单一的蓝色,XAML代码(省略了Window标签)如下:
<Grid> <Rectangle x:Name="rectangle" Fill="Blue" HorizontalAlignment="Left" Height="100" Margin="70,17,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/> </Grid>
上面的“Blue”最终被翻译成了SolidColorBrush对象,等效C#代码如下:
SolidColorBrush sBrush = new SolidColorBrush(); sBrush.Color = Colors.Blue; this.rectangle.Fill = sBrush;
使用属性元素
属性元素指的是某个非空标签的一个元素(夹在起始标签和结束标签之间的一些子级标签)对应这个标签的一个属性,即以元素的形式来表达一个实例的属性。
上面Attribute赋值的例子可以用属性元素的方式实现,XAML(省略了Window标签)代码如下:
<Grid> <Rectangle x:Name="rectangle" HorizontalAlignment="Left" Height="100" Margin="70,17,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"> <Rectangle.Fill> <SolidColorBrush Color="Blue"/> </Rectangle.Fill> </Rectangle> </Grid>
属性元素语法的优势在属性是复杂对象时才能体现出来,比如线性渐变画刷填充矩阵代码(省略了Window标签):
<Grid> <Rectangle x:Name="rectangle" HorizontalAlignment="Left" Height="100" Margin="70,17,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"> <Rectangle.Fill> <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.2" Color="LightBlue"/> <GradientStop Offset="0.7" Color="Blue"/> <GradientStop Offset="1.0" Color="DarkBlue"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Grid>
扩展:标记扩展(Markup Extensions)
上面大多数赋值都是为属性生成一个新对象,当需要为对象的属性进行特殊类型赋值时就需要使用标记扩展了,如:
- 把同一个对象赋值给两个对象的属性
- 给对象的属性赋一个null值
- 将一个对象的属性值依赖在其他对象的某个属性上
标记扩展是一种特殊的Atribule=Value语法,Value字符串是由一对花括号及其括起来的内容组成,XAML编译器会对这样的内家做出解析并生成相应的对象。
使用Binding类的实例将TextBox的Text属性依赖在Slider的Value上,当Slider的滑块滑动时TextBox就会显示Slider当前的值,XAML代码(省略了Window标签)如下:
<Grid> <StackPanel HorizontalAlignment="Left" Height="115" Margin="10,10,0,0" VerticalAlignment="Top" Width="205"> <TextBox Height="23" TextWrapping="Wrap" Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}"/> <Slider x:Name="slider1" Margin="0,5,0,0"/> </StackPanel> </Grid>
Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}"就是标记扩展,对象的数据类型名是紧邻左花括号的字符串,对象的属性由一串以逗号连接的子字符串负责初始化(不加引号)。
标记扩展也是对属性的赋值,完全可以使用属性标签的形式来替换标记扩展,但基本上没人这么做(太不简洁了)。
只有MarkupExtension类的派生类(直接或间接均可)才能使用标记扩展语法来创建对象,后面会主要说明下面几种:
- MarkupExtension的直接派生类并不多,它们是:
- System.Windows.ColorConvertedBitmapExtension
- System.Windows.DynamicResourceExtension
- System.Windows.ResourceKey
- System.Windows.StaticResourceExtension
- System.Windows.TemplateBindingExtension
- System.Windows.ThemeDictionaryExtension
- System.Windows.Data.BindingBase
- System.Windows.Data.RelativeSource
- System.Windows.Markup.ArrayExtension
- System.Windows.Markup.NullExtension
- System.Windows.Markup.StaticExtension
- System.Windows.Markup.TypeExtension
扩展:使用TypeConverter 类映射Atribute与Property
有时候我们有一些自定义的类需要使用XAML语言进行声明,并允许它的Property与XAML标签的Atribute互相映射,那就需要为这些Property准备适当的转换机制。
下面以自定义类Human为例演示TypeConverter的用法,XAML代码(省略了Window标签)如下:
<Window.Resources> <local:Human x:Key="human" Child="AB" /> </Window.Resources> <Grid> <Button Content="Button" HorizontalAlignment="Left" Margin="61,40,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/> </Grid>
//按钮事件 private void Button_Click(object sender, RoutedEventArgs e) { Human h = (Human)this.FindResource("human"); MessageBox.Show(h.Child.Name); } /// <summary> /// 以绑定转换器的目标类型Human(TypeConverter是TypeConverterAttribute的简写) /// </summary> [TypeConverter(typeof(StringToHumanlypeConverter))] public class Human { public string Name { get; set; } public Human Child { get; set; } } /// <summary> /// 字符串转目标类型Human的转换器 /// </summary> public class StringToHumanlypeConverter : TypeConverter { //运行时转换源类型为此目标类型,缺少此方法的重载会使运行时类型转换异常 public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { Human h = new Human(); h.Name = value as string; return h; } return base.ConvertFrom(context, culture, value); } //设计时判断是否支持源类型到目标类型的转换,缺少此方法的重载会使UI设计器异常 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } }
需要注意的是TypeConverter的ConvertFrom、CanConvertFrom两个方法必须同时重载,只有前者会导致设计时界面异常、运行时数据转换正常,只有后者会导致设计时界面正常、运行时数据转换异常。
事件处理器
一个XAML标签对应着一个对象时,这个标签的一部分Atribute会对应这个对象的Property,还有一部分Attribute对应着对象的事件(Event)。
<Grid> <Button x:Name="button1" Content="Button" HorizontalAlignment="Left" Margin="61,40,0,0" VerticalAlignment="Top" Width="75" Click="button1_Click"/> </Grid>
注:右键Click="Button_Click"->“转到定义”,会自动生成一个空的事件处理函数。
等效的C#代码如下所示:
Button buttonl = new Button(); buttonl.Click += new RoutedEventHandler(buton1_Click);
由于C#的partial类和XAML标签的x.Class特征,开发软件时完全可以把用于实现程序逻辑的C#代码放在一个文件里,把用于描述程序U1的XAML代码放在另一个文件里,并且让事件性Attribute充当XAML与C#之间沟通的纽带。这种将逻辑代码与UI代码分离、隐藏在UI代码后面的形式就叫作“代码后置”(Code-Behind)。当然,事件代码也可以使用标签x:Code直接写到XAML里面。
导入程序集及引用命名空间
在XAML中引用命名空间的语法是:
xmlns:映射名="clr-namespace:类库中命名空间的名字;assembly=类库文件名"
- xmlns是用于在XAML中声明命名空间的Attribute,从XML语言继承而来(XML Namespace的缩写)。
- 冒号后的映射名是可选的,但由于可以不加映射名的默认命名空间已经被WPF的主要命名空间占用,所以所引用的命名空间都需要加上这个映射名。映射名可以自由选择,建议使用类库中命名空间的原名或者缩写。
- 引号中的字符串值确定了要引用的是哪个类库以及类库中的哪个命名空间。
使用引用命名空间中的类的语法如下:
<映射名:类名>.…</映射名:类名>
C#中也可以给命名空间设置映射名,但没什么意义,如:
using Cmn=Common; using Cu=Controls;
XAML的注释
XAML的注释语法亦继承自XML,语法是:
<!--需要被注释掉的内容-->
- XAML注释只能出现在标器的内容区域,即只能出现在开始标签和结来标签之阔。
- XAML注释不能用于注释标签的Attribute。
- XAML注释不能嵌套。