【WPF学习】第二章 XAML的属性和事件
1、简单属性和类型转换器
在添加文本框时,经常会设置文本框对齐方式、字体、字体大小和页边距等信息, 如下文本框示例所示:
<TextBox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="10,10,13,10" TextWrapping="Wrap" Grid.Row="0" FontFamily="Verdana" FontSize="24"> [Please Question here!] </TextBox>
为使上面的设置起作用,System.Windows.Controls.TextBox类必须提供以下属性:VerticalAlignment、HorizontalAlignment、FontFamily、FontSize、Foreground。为使这些功能起作用,XAML解析器需要执行比表面上看起来更多的工作。XML特性中的值总是纯文本字符串。但对象的属性可以是任何.NET类型。在上面的示例中,有两个属性是枚举类型(VerticalAlignment属性和HorizontalAlignment属性),一个为字符串类型(FontFamily属性),一个为整形(FontSize属性),还有一个为Brush对象(Foreground属性)。
为了关联字符串值和非字符串属性,XAML解析器需要执行转换。由类型转换器执行转换,类型转换器是从.NET 1.0就已经引入的.NET基础结构的一个基本组成部分。
实际上,类型转换器在这个过程中扮演着重要角色——提供了实用的方法,这些方法可将特定的.NET数据类型转换为任何其他.NET类型,或将其他任何.NET类型转换为特定的数据类型。XAML解析器通过以下两个步骤来查找类型转换器:
(1)检查属性声明,查找TypeConverter特性(如果提供了TypeConverter特性,该特性将制定哪个类可执行转换)。例如,当实用诸如Foreground这样的属性时,.NET将检查Foreground属性的声明。
(2)如果在属性声明中没有TypeConverter特性,XAML解析器将检查对应数据类型的类声明。例如,Foreground属性实用一个Brush对象。由于Brush类使用TypeConverter(typeof(BrushConverter))特性声明进行了修饰,因此Brush类及其子类使用BrushConverter类型转换器。
如果属性声明或类声明都没有与其关联的类型转换器,XAML解析器会生成错误。
2、复杂属性
虽然类型转换器便于使用,但不能解决所有的实际问题。例如,有些属性是完备的对象,这些对象具有自己的一组属性。尽管创建供类型转换器使用的字符串形式是可能的,但使用这样的方法时语法可能十分复杂,并且容易出错。
幸运的是,XAML提供了另外一种选择:属性元素语法(property-element syntax)。使用属性元素法,可添加名称形式为Parent.PropertyName的子元素。例如,Grid控件有一个Backgroud属性,该属性允许提供用于绘制控件背景区域的画刷。如果希望使用更复杂的画刷——比单一固定颜色填充更高级的画刷——就需要添加名为Grid.Background的子标签。如下所示:
<Grid.Background> <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.00" Color="Red"/> <GradientStop Offset="0.5" Color="Indigo"/> <GradientStop Offset="1.0" Color="Violet"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Grid.Background>
为了定义所需要的渐变色,需要创建LinearGraduebtBrush对象。LinearGraduebtBrush类是WPF名称空间集合中的一部分,所以可为标签继续使用默认的XML名称空间。
但是,只是创建LinearGraduebtBrush对象还不够——还需要为其指定渐变的颜色。通过使用GradientStop对象的结合填充LinearGraduebtBrush.GradientStops属性可完成这一工作。
3、标记扩展
对大多数属性而言,XAML属性语法可以工作的非常好。但是有些情况下,不可能硬编码属性值。例如,可能希望将属性值设置为一个已经存在的对象,或者可能希望通过将一个属性绑定到另外一个控件来动态地设置属性值。这两种情况都需要使用标记扩展——一种以非常规的方法设置属性的专门语法。
标记扩展可用来嵌套标签或XML特性中(用于XML特性的情况更常见)。当用在特性中时,它们总是被花括号{}包围起来。例如,下面的标记演示了如何使用标记扩展,它允许引用另外一个类中的静态属性:
<Button Foreground="{x:Static SystemColors.ActiveCaptionBrush}"></Button>
标记扩展使用{标记扩展类 参数}语法。在上述的示例中,标记扩展是StaticExtension类(根据约定,在引用扩展类时可以省略最后一个单词Extension)。x前缀指示在XAML名称空间中查找StaticExtension类。还有一些标记扩展是WPF名称空间的一部分,它们不需要x前缀。
所有标记扩展都由继承自System.Windows.Markup.MarkupExtension基类的类实现。MarkupExtension基类十分简单——它提供了一个简单的ProvideValue()方法类获取所期望的数值。换句话说,当XAML解析器遇到上述语句时,它将创建StaticExtension类的一个实例,然后调用ProvideValue()方法获取静态对象。最后找到按钮的Foreground属性。
4、附加属性
除普通属性外,XAML还包括附加属性(attached property)的概念——附加属性是可用于多个控件但在另外一个类中定义的属性。在WPF中,附加属性常用语控件布局。
每个控件都有各自固有的属性。当在容易中方式控件时,根据容器的类型控件会获得额外特征(例如,如果在网格中放置一个文本框,就需要选择文本框放在网格控件中的哪个单元格中),使用附加属性设置这些附加的细节。
附加属性始终使用包含两个部分的命名格式:定义类型.属性名。这zho9ng包含两部分的命名语法使XAML解释器能区分开普通属性和附加属性。
如下代码中,Grid.Row=“0”就是属于附加属性。
<TextBox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="10,10,13,10" TextWrapping="Wrap" Grid.Row="0" FontFamily="Verdana" FontSize="24"> [Please Question here!] </TextBox> <Button VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,0,0,20" Grid.Row="1" Width="127" Height="23" Name="btnAnswer" Click="btnAnswer_Click"> Ask the Eight Ball </Button> <TextBox Grid.Row="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" TextWrapping="Wrap" IsReadOnly="True" Margin="10,10,13,10" FontFamily="Verdana" FontSize="24" Foreground="Green"> [Answer will appear here!] </TextBox>
附加属性根本不是真正的属性。它们实际上被转换为方法调用。XAML解析器采用以下形式调用静态方法:DefiningType.SetPropertyName()。例如,在上面的XAML代码段中,定义类型是Grid类,并且属性是Row,所以解析器调用Grid.SetRow()方法。
当调用SetPropertyName()方法时,解析器传递两个参数:被修改的对象以及指定的属性值。例如,当为Textbox控件设置Grid属性时,XAML解析器执行以下代码:
Grid.SetRow(txtQuestion,0);
这种方式(调用定义类型的一个静态方法)隐藏了实际发生的操作,使用起来非常方便。
这样的技巧之所以能够凑效,是因为与其他所有WPF控件一样,TextBox控件继承自DependencyObject基类。
2.5 特殊字符与空白
XAML受到XML规则的限制。例如,XML特别关注一些特殊字符,如&、<和>。如果使用使用这些字符设置元素的内容,将遇到麻烦,因为XAML解析器认为您正在处理其他事情——例如,创建嵌套的元素。
例如,假设需要创建一个包含<Click Me>文本的按钮,下面的标记是无法奏效的。
<Button> <Click Me> </Button>
此处的问题在于,上面的标记看起来像正在试图创建一个名为Click,并且带有<Click>文本的元素。解决问题的方法是用实体引用代替那些特殊字符,实体引用是XAML解析器能够正确解析的特定字符编码。如下表列出了可能选用的字符实体。主义,只有当使用特殊设置属性值时,才需要使用引号字符实体,因为引号用于指示特殊指的开始和结束。
特殊字符 | 字符实体 |
小于号(<) | < |
大于号(>) | > |
&符号(&) | & |
引号(") | " |
可以将上面的XAML代码段修改为:
<Button> <Click Me > </Button>
特殊字符并非使用XAML的唯一障碍。另一个问题是空白的处理。默认情况下,XAML折叠所有空白,这意味着包含空格、Tab键以及硬回车的长字符串将被转换为单个空格。而且,如果在元素内容之前或者之后添加空白,将完全忽略这个空格。在Eight BallAnswer示例中您将看到这种情形。在按钮和两个文本框中的文本,使用硬回车字符从XAML标签中分离出来,并使用Tab字符使标记更加清晰易读。但多余的空格不会再显示在用户界面中。
有时这并不是所有期望的结果。例如,可能希望在按钮文本中包含一系列空格。在这种情况下,需要为元素使用xml:space="preserve"特性。
xml:space特性是XML标准的一部分,是一个要么包括全部、要么什么都不包括的设置。一旦使用了该设置,元素内的所有空字符串都将被保留。比如下面的标记:
<TextBox xml:space="preserve"> [There is a lot of space inside these marks" ".] </TextBox>
2.6 事件
用于关联事件处理程序的语法为:事件名=“事件处理程序方法名”。
例如,Button控件提供了Click事件。可使用如下所示的标记关联事件处理程序:
<Button Click="btnAnswer_Click"> Ask the Eight Ball </Button>
上面的标记假定在代码隐藏类中有名为btnAnswer_Click的方法。事件处理程序必须具有正确的签名(也就是说,必须匹配Click事件的委托)。下面是一个符合要求的方法:
private void btnAnswer_Click(object sender, RoutedEventArgs e) { }
许多情况下, 将使用特性为同一元素设置属性和关联事件处理程序。WPF总是遵循以下顺序:首先设置Name属性(如果设施的话),然后关联任意事件处理程序,最后设置其他属性。这意味着,所有对属性变化作出响应的事件处理程序在第一次设置属性时都会被触发。
完整XAML代码如下所示:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Eight Ball Answer" Height="328" Width="412"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="10,10,13,10" TextWrapping="Wrap" Grid.Row="0" FontFamily="Verdana" FontSize="24"> [Please Question here!] </TextBox> <Button VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,0,0,20" Grid.Row="1" Width="127" Height="23" Name="btnAnswer" Click="btnAnswer_Click"> Ask the Eight Ball </Button> <TextBox Grid.Row="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" TextWrapping="Wrap" IsReadOnly="True" Margin="10,10,13,10" FontFamily="Verdana" FontSize="24" Foreground="Green"> [Answer will appear here!] </TextBox> <Grid.Background> <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.00" Color="Red"/> <GradientStop Offset="0.5" Color="Indigo"/> <GradientStop Offset="1.0" Color="Violet"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Grid.Background> </Grid> </Window>