4 WPF学习---系统的学习XAML语法

时间:2021-01-04 16:42:55

转载:http://blog.csdn.net/fwj380891124/article/details/8093001

1,XAML文档的树形结构:

UI在用户眼里面是个平面结构。如下图所示,在用户眼里看来,这个界面就是一个窗体里面平铺了4个文本框和一个按钮的界面。

4 WPF学习---系统的学习XAML语法

在传统的Visual C++、Delphi、Visual Basic6.0和Windows Form程序员的思维里,UI也是一个平面的结构。因此,程序员要做的事情就是根据美工给的给定的UI布局把控件安置在窗体的表面,并用使用长度,宽度和间距把控件对齐。

与传统的设计思维不同,XAML使用树形逻辑结构来描述UI,下面是用来描述界面布局的XAML代码:

<Window x:Class="WpfApplication2.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="331" Width="454">
<StackPanel Height="279" Name="stackPanel1" Width="402" Margin="5" Background="AliceBlue">
<TextBox Height="23" Name="textBox1" Width="260" Margin="5"/>
<TextBox Height="23" Name="textBox2" Width="259" />
<StackPanel Height="100" Name="stackPanel2" Width="273" Orientation="Horizontal">
<TextBox Height="23" Name="textBox3" Width="120" Margin="5"/>
<TextBox Height="23" Name="textBox4" Width="120" Margin="5"/>
</StackPanel>
<Button Height="33" Name="button1" Width="99" Margin="10">
<Image Source="/WpfApplication2;component/Images/track.png" />
</Button>
</StackPanel>
</Window>

因为代码中有许多对Attribute的属性,所以结构看起来并不是那么清晰。如果我们把对Attribute的赋值都去掉,那么上面的代码就显现了它的树形框架结构。
     

<Window>
<StackPanel>
<TextBox />
<TextBox />
<StackPanel>
<TextBox />
<TextBox />
</StackPanel>
<Button>
<Image />
</Button>
</StackPanel>
</Window>

如果用一张图来表示上面的那段代码,它会是下面这个样子

4 WPF学习---系统的学习XAML语法

有意思的是,针对一个“看上去一样”的UI布局,XAML代码不一定是唯一的。拿上面的UI代码布局来说,我们还可以使用不同的XAML代码来描述它。

<Window x:Class="WpfApplication2.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window3" Height="348" Width="538">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="266*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="42" />
<RowDefinition Height="48" />
<RowDefinition Height="66" />
<RowDefinition Height="153" />
</Grid.RowDefinitions>
<TextBox HorizontalAlignment="Center" Margin="10,10,0,0" Name="textBox1" VerticalAlignment="Top" Grid.ColumnSpan="2"/>
<TextBox HorizontalAlignment="Center" Margin="10,10,0,0" Name="textBox2" VerticalAlignment="Top" Grid.Row="1" Grid.ColumnSpan="2"/>
<TextBox HorizontalAlignment="Right" Margin="10,10,0,0" Name="textBox3" VerticalAlignment="Top" Grid.Row="2" Grid.Column="0"/>
<TextBox HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBox4" VerticalAlignment="Top" Grid.Row="2" Grid.Column="1"/>
<Button HorizontalAlignment="Center" Margin="10,10,0,0" Name="button1" VerticalAlignment="Top" Grid.Row="3" Grid.ColumnSpan="2">
<Image Source="/WpfApplication2;component/Images/track.png" />
</Button>
</Grid>
</Window>

精简后的代码是:

<Window>
<Grid>
<TextBox />
<TextBox />
<TextBox />
<TextBox />
<Button>
<Image />
</Button>
</Grid>
</Window>

框架变成了如图所示的样子:

4 WPF学习---系统的学习XAML语法

虽然两段代码对UI的实现方式不同,但是框架都是树形的,以<Window>对象为根节点,一层一层向下包含。这种树形结构对于WPF整个体系都具有非常重要的意义,它不但影响着UI的布局设计,还深刻的影响着WPF的属性(Property)子系统和事件(Event)子系统等方方面面。在实际的编程过程中,我们经常要在这棵树上进行按名称查找元素,获取父/子节点等操作,为了方便操作这棵树,WPF基本类库为程序员准备了VisualTreeHelper和LogicTreeHelper两个助手类(Helper Class),同时还在一些重要的基类里封装了一些专门用于操作这棵树的方法。

UI布局:

你也许可能会问:既然有这么多方法可以实现同一个UI,到底应该选择哪一种方式来实现UI呢?实际上,设计师给出的UI布局是软件的一个静态快照(Static Snap),这个静态快照加上用户有可能动态操作才能够构成选择实现布局形式的完整依据。

拿上面两段代码来说,如果你希望用户在改变窗体大小后需要等比例缩小自己内部控件的尺寸,那么你选择第二种Grid;如果只希望控件在界面上做一个简单的排列,第一种足矣 StackPanel。

2,XAML中为对象赋值的方法

XAML是一种声明性语言,XAML会为每一个标签创建一个与之对应的对象,对象创建之后要对它的属性进行必要的初始化之后才有使用意义。因为XAML语言不能够编写程序的运行逻辑,所以一份XAML文档除了使用标签声明对象就是初始化对象属性了。

注意:

XAML中对对象赋值总共有两种方法:

A:使用字符串进行简单赋值。

B:使用属性元素(Property Element)进行复杂赋值。

我用一个<Rectangle>标签的Fill为例来介绍这两种方法:

2.1   使用标签的Attribute为对象属性赋值

前面我们已经知道,一个标签的Attribute有一部分与对象的Property对应,<Rectangle>标签里面的Fill这个Attribute就是这样,他与Rectangle类对象的Fill属性对应,在MSDN文档库里可以查询到,Retangle类的Fill类型是一个Brush。Brush是一个抽象类,凡是以Brush为基类的类都可以成为Fill的属性值。Brush的派生类有很多:

  • SolidColorBrush:单色画刷。
  • LinearGradientBrush:线性渐变画刷。
  • RadialGradientBrush:径向渐变画刷。
  • ImageBrush:位图画刷。
  • DrawingBrush:矢量图画刷。
  • VisualBrush:可视元素画刷。

下面这个例子是使用SolidColorBrsh和LinearGradientBrush两种。

我们先学习使用字符串对Attribute的简单赋值,假设我们的Rectangle只需要填充成单一的蓝色,那么我们只需要简单的写成:

<Window x:Class="WpfApplication2.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4" Height="335" Width="472">
<Grid>
<Rectangle Height="127" HorizontalAlignment="Left" Margin="103,96,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="243" Fill="Blue"/>
</Grid>
</Window>

运行效果如下图:

4 WPF学习---系统的学习XAML语法

我们看到,blue这个字符串最终被翻译成了SolidcolorBrush并赋值给了Rectangle对象。换成C#代码是这样。

SolidColorBrush brush = new SolidColorBrush();
brush.Color = Colors.Blue;
this.rectangle1.Fill = brush;

需要注意的是,这种Attribute=Value的赋值时,由于XAML语法有限,Value只能是一个字符串值,这就引发了下面两个问题:

A,如果一个类可以使用XAML类来进行声明,并允许它的的Property可以和它的Attribute互相映射,那就需要为这些Property准备适当的转换机制。

B,由于Value是个字符串,所以其格式复杂程度有限,尽管可以在转换机制里面包含一定的按格式解析字符串的功能以便转换成较复杂的目标对象,但这会让最终的XAML使用者头疼不已,因为他们不得不在一个没有编码辅助的情况下手写一个格式复杂的字符串来满足需求。
第一个问题的解决方式是使用TypeConverter类的派生类,在派生类里面重新TypeConverter的一些方法,第二个问题的解决办法就是使用属性元素(Property Element)。

2.2使用TypeConverter类将XAML标签的Attribute与对象的Property进行映射

注意

本小节的例子对于初学者来说理解起来比较困难而且实用性不大,主要是为喜欢刨根问底的WPF开发人员准备的,初学者可以跳过这一节。

首先我们准备一个类:

public class Human
{
public string Name{get;set;}
public Human Child { get; set; }
}

这个类具有连个属性:

String类型的Name;

Human类型的Child;

现在我们期望的是,如果我们在XAML里面这样写:

<Window.Resources>
<local:Human x:Key="human" child="ABC"></local:Human>
</Window.Resources>

则能够为Human实例的Child属性赋一个Human类型的值,并且Child.Name就是这个字符串的值。

我们点看看直接写行不行。在UI上添加一个Button,并在Click事件里面写上:

Human human = (Human)this.FindResource("human");
MessageBox.Show(human.Child.Name);

编译没有问题,但是在单击按钮的时候会弹出异常,告诉Child不存在,为什么Child不存在呢?原因很简单,Human的Child属性是Human类型,而XAML代码中的ABC是个字符串,编译器不知道如何将一个字符串转换成一个Human实例。那我们应该怎么办呢?办法是使用TypeConverter和TypeConvertAttribute这两个类。

首先,我们要从TypeConvert派生出自己的类,并重写它的ConverFrom方法。这个方法有一个参数名叫做Value,这个值就是XAML文档里面为其设置的值。我们要做的就是将这个值翻译成合适对象的值并赋值给对象的属性:

public class StringToHumanTypeConverter:TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if(value is string)
{
Human human = new Human();
human.Name = value as string;
return human;
}
return base.ConvertFrom(context, culture, value);
}
}

有了这个类还不够,你还需要使用TypeConvertAtrribute这个特征类把StringToHumanTypeConverter这个类"粘贴"到作为目标的Human类上。

[TypeConverterAttribute(typeof(StringToHumanTypeConverter))]
public class Human
{
public string Name{get;set;}
public Human Child { get; set; }
}

因为特征类在书写的时候可以省略Attribute这个词,所以也可以写作:

[TypeConverter(typeof(StringToHumanTypeConverter))]
public class Human
{
public string Name{get;set;}
public Human Child { get; set; }
}

但这样写,我们需要认清括号里是TypeConverterAttribute而不是TypeConverter。

完成之后我们再单击按钮,我们想要的结果出来了,如下图:

4 WPF学习---系统的学习XAML语法

注意:

TypeConverter类的使用远远不只是重载一个ConvertFrom方法这么简单,为了配合这个方法的运行,还需要重载其它几个方法。详细的使用方法请参见TypeConvert类库文档。

2.3属性元素

在XAML中,非空标签均具有自己的内容(Content)。标签的内容就是指夹在起始标签和结束标签中的一些子级标签,每个子级标签都是父级标签中的一个元素(Element),简称为父级标签的一个元素。顾名思义,属性元素指的就是某个标签的一个元素对应这个标签的一个属性,即已元素的形式来表达一个实例的属性。代码描述为:

<ClassName>
<ClassName.PropertyName>
</ClassName.PropertyName>
<ClassName>

这样,这个标签的内部就可以使用对象(而不仅限于字符串)进行赋值了。

如果把上面的例子用标签语法改写一下,XAML将是这样:

<Window x:Class="WpfApplication2.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="Window4" Height="335" Width="472">
<Window.Resources>
<local:Human x:Key="human" Child="ABC"></local:Human>
</Window.Resources>
<Grid>
<Rectangle Height="127" HorizontalAlignment="Left" Margin="103,96,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="243">
<Rectangle.Fill>
<SolidColorBrush Color="Blue"></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="180,256,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window></span>

效果和之前的效果一样,对于简单的赋值而言,属性标签体现不出来什么优势,反而让代码看起来有点冗长。但遇到属性复杂的对象这种语法的优势就体现出来了,如使用线性渐变来填充这个矩形:

<Window x:Class="WpfApplication2.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="Window4" Height="335" Width="472">
<Window.Resources>
<local:Human x:Key="human" Child="ABC"></local:Human>
</Window.Resources>
<Grid>
<Rectangle Height="127" HorizontalAlignment="Left" Margin="103,96,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="243">
<Rectangle.Fill> <LinearGradientBrush>
<LinearGradientBrush.StartPoint>
<Point X="0" Y="0.5"></Point>
</LinearGradientBrush.StartPoint>
<LinearGradientBrush.EndPoint>
<Point X="1" Y="1.5"></Point>
</LinearGradientBrush.EndPoint>
<LinearGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="1" />
<GradientStop Color="White" Offset="0" />
<GradientStop Color="#CD524A4A" Offset="0.434" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="180,256,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>

LinearGradientBrush的GradientStops属性是一个GradientStop的集合(GradientStopCollection),即一些列的矢量渐变填充点。在这些填充点之间,系统会自己进行插值计算,计算出过渡色彩。填充矢量方向是StartPoint和EndPoint两个属性(类型为Point)的连线方向,矩形的左上脚为0,0,右下角为1,1。这段代码中,针对这三个属性都是使用属性标签的赋值方法。

古语道,“过犹不及”。上面的代码为了突出属性元素我将所有的属性都使用属性元素的方法进行赋值,结果代码的可读性一落千丈。经过优化,代码变成了如下,少了1/3。

<Window x:Class="WpfApplication2.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="Window4" Height="335" Width="472">
<Window.Resources>
<local:Human x:Key="human" Child="ABC"></local:Human>
</Window.Resources>
<Grid>
<Rectangle Height="127" HorizontalAlignment="Left" Margin="103,96,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="243">
<Rectangle.Fill>
<LinearGradientBrush></pre><pre name="code" class="html"> <LinearGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="1" />
<GradientStop Color="White" Offset="0" />
<GradientStop Color="#CD524A4A" Offset="0.434" />
</LinearGradientBrush.GradientStops>
</pre><pre name="code" class="html"> </LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="180,256,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window> </span>

这里几个简化XAML的技巧:

能用Atrribute=value方式进行赋值的就不要使用属性元素。

充分利用默认值,除去冗余。

充分利用XAML的简写方式:XAML的简写方式很多,需要在工作中慢慢积累。