我在上一篇博文中说过,XAML是一种基于XML的用来创建和初始化.NET对象的语言。虽然XAML可以在更多的CLR类型中进行运用,但在Silverlight中它是以一种人类可创作的方式来描述UI。
一, 初识XAML:
XAML代码:
1 <UserControl x:Class="SilverlightApplication1.MainPage" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d=http://schemas.microsoft.com/expression/blend/2008 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 mc:Ignorable="d" 7 d:DesignHeight="300" d:DesignWidth="400"> 8 <!--http://schemas.microsoft.com/winfx/2006/xaml/presentation:核心Silverlight命名空间 9 http://schemas.microsoft.com/winfx/2006/xaml:是XAML命名空间 10 --> 11 12 <Grid x:Name="LayoutRoot" Background="White"> 13 <Button 14 x:Name="button" 15 Width="200" 16 Height="25" 17 Click="button_Click" 18 > 19 Click me,baby,one more time! 20 </Button> 21 </Grid> 22 </UserControl>
运行结果,如图:
与XAML代码等效的C#代码:
1 partial class MainPage : UserControl//类MainPage从UserControl继承
2 {
3 Button button;//声明一个Button类对象
4
5 void InitializeComponent()
6 {
7 //初始化button
8 button = new Button();
9 button.Width=200;
10 button.Height=25;
11 button.Click+=button_Click;//添加事件
12
13 this.AddChild(button);//调用AddChild()方法将button添加到当前对象中
14 }
15 }
从以上这个简单的实例中我们可以得出如下两点认识:
1,通常,一个XAML元素就是一个.NET类名,一个XAML属性(Attribute)就是一个类的属性(Property)名或者一个类的事件名,XAML被尽可能地设计成从XML到.NET的直接映射.这一点认识非常重要.
2,我在上一篇中定义XAML的时候说过,XAML语言是基于XML语言的,所以它的表现形式与XML非常相似.
有了以上两点认识后,下面我们开始XAML的学习了.
为了方便解释后面的内容,我在这里再创建一个基本的XAML例子,作为后面内容的公共实例:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Ellipse Fill="LightBlue"/>
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
</Grid>
</UserControl>
二,命名空间:
当在XAML文件中使用<TextBlock>元素时,Silverlight解析器会识别出来你想要创建TextBlock类的一个实例.然而,它无需知道使用的TextBlock类是什么的.毕竟,即便Silverlight命名空间只包含一个单独的名为TextBlock的类,但谁也不敢保证你不会创建一个自定义的同名的类.明确地说,你需要一种方法来指明Silverlight命名空间信息,从而明确使用元素.这就是为什么要定义命名空间的原因.
在Silverlight中,通过将XML命名空间映射到Silverlight命名空间来解析类的.这一点的理解很重要.
下面我还是拿上面的例子来讲解.
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
</UserControl>
这里根元素<UserControl>中定义了两个最基本的命名空间(当然还有其它命名空间后面会介绍).
1,http://schemas.microsoft.com/winfx/2006/xaml/presentation是核心Silverlight命名空间.它包含所有的Silverlight中的类,包括Grid,StackPanel,TextBlock以及Button,通常,这个命名空间没有使用命名空间前缀来声明,因此它就成了整个XAML文档的默认命名空间,也就是说,除非你特别指明,每一个元素都会被自动放入这个命名空间中.
2,http://schemas.microsoft.com/winfx/2006/xaml是XAML命名空间.它包含多种XAML功能,可以让你控制文档如何被解析.这个命名空间通常会映射到x前缀.
上面两个命名空间基本上就可以让你访问Silverlight元素的核心库了.不过如果你觉得不够,你还可以自定义命名空间,下面是自定义命名空间的语法.
语法:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns:w="clr-namespace:Widgets;assembly=Widgets"
...
XML命名空间声明设定了3方面的信息:
1,XML命名空间前缀.注意你定义的命名空间前缀不要与其它的命名空间前缀冲突就可以了.
2,.NET命名空间.在此例中.类被放置在Widgets命名空间中.
3,程序集.在此例中,所使用的类是Widgets.dll程序集的一部分.假设你已经在Silverlight应用程序中添加了指向Widgets程序集的引用,它将会被自动包含进最终的XAP包里.一旦将.NET命名空间映射到XML命名空间,你就可以在XAML文档的任何地方使用它.例如,如果Widgets命名空间中包含名为HotButton的控件,你可以像下面这样创建实例了.
<w:HotButton Text="Click me" Click="DoSomething"></w:HotButton>
三,生成类:
有一个在根元素的x:Class属性,在这里再展示一遍:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
</UserControl>
我分以下几点来解释:
1,前缀"x:"是标准XML缩写,它表示这个特定的属性是由命名空间中的xmlns:x属性所指定.
2,对于XAML编译器来说,x:Class属性就意味着需要生成基于XAML文件的类定义,而x:Class属性就是决定所生成的类名字,并且将它派生自根元素.在这里,所生成的类名为MainPage,它的基类为UserControl.
3,你不一定要指定一个x:Class属性.假如我们忽略这个示例中的属性,那么根对象的类型将是UserControl,而不是生成的MainPage类.不过一般会指定这个属性.
4,在选择了生成一个类时,它会提供一个通过XAML描述的简便方法来创建对象树.由于我们所生成的每一个MainPage实例都将包含一系列由XAML指定的对象,因此我们只需要使用普通的对象构造语法,如:
MainPage myMainPage=new MainPage();
以上就是今天总结的内容,希望通过这一篇的学习能对XAML有一个初步的认识,为后面的学习打下坚实的基础!
在继续总结后面的内容之前,我们先来看一下前面漏掉的一个知识点,那就是分部类,下面我们来看一下什么是分部类,以及为什么要用分部类。
一,分部类:
我们还是拿之前的例子来看,代码如下。
XAML代码:
<UserControl x:Class="SilverlightApplication1.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<!--http://schemas.microsoft.com/winfx/2006/xaml/presentation:核心Silverlight命名空间
http://schemas.microsoft.com/winfx/2006/xaml:是XAML命名空间
-->
<Grid x:Name="LayoutRoot" Background="White">
<Button
x:Name="button"
Width="200"
Height="25"
Click="button_Click"
>
Click me,baby,one more time!
</Button>
</Grid>
</UserControl>
Code-behind代码:
public partial class MainPage : UserControl//分部类关键字partial
{
public MainPage()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
//todo:添加Click事件处理代码
MessageBox.Show("button被单击了!");
}
}
大家可能已经注意到MainPage类名前的关键字partial,没错,它在这边声明的就是分部类,从这个例子中我们能够了解到什么是分部类以及分部类的作用。
1,什么是分部类?
将一个类的定义拆分到两个或多个源文件中。在类声明前添加关键字partial.
2,分部类的作用?
一个类分布在多个独立源文件中可以让多位程序员同时对该类进行处理,在这里,实现了界面和行为的分离。
关键字partial使得编译器将XAML文件生成的类(在这里指XAML文件)与人为生成的类(在这里指Code-behind文件)结合起来形成一个完整的类。这两部分代码是成对出现并且相互依赖的。XAML文件中定义的部分MainPage类依赖于隐藏代码中的类来调用InitializeCompoent方法,并处理事件。隐藏代码中的类则依赖于XAML文件中定义的分部MainPage类来实现InitializeCompoent,从而得到了主窗体的"外观"(同时包含了相关的子控件).
二,子元素:
在讲解子元素之前,我们先来看一下完整的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="MainWindow" Height="300" Width="300">
<Grid>
<Ellipse/>
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
</Grid>
</Window>
其中子元素的XAML代码为:
<Grid>
<Ellipse/>
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
</Grid>
不管什么时候提供嵌套内容,XAML编译器都需要父类型(在这里是Window)或者是通过ContentPropertyAttribute来注释的基类。在这个示例中,虽然Windows没有这个属性,但是它的ContentControl基类有这个属性,如下。
处理子内容:
[ContentProperty("content"),...]
public class ContentControl:Control
{
...
public Object Content{get{...} set{...}}
}
这个属性将会为XAML编译器提供包含子内容的属性名。因此,在这个示例中,编译器将会为一个将要创建并且指派到Content属性上的Grid对象进行排列,如下是相应的代码:
窗口内容:
MainWindow myWindow=new MainWindow(); //创建MainWindow类的对象
Grid g=new Grid(); //创建Grid类对象
myWindow.Content=g;
Content属性是属性Object类型,这就意味着Window元素只支持单个子元素。诸如面板之类的可以包含多个子元素的元素只需指定一个带有集合类型的元素作为内容属性,如:
处理多个子元素:
[ContentProperty("Children"),...]
public class Panel:FrameworkElement
{
...
public UIElementCollection Children{get{...}}
}
由于Panel是Grid的一个基类,因此这就告诉了我们XAML编译器是如何在这个示例中将子元素添加到Grid的,说明代码如下。
为Grid添加内容:
Grid g=new Grid(); //创建Grid类对象
myWindow.Content=g;
Ellipse e=new Ellipse();//创建Ellipse类对象
TextBlock t=new TextBlock();//创建TextBlock 类对象
...
g.Children.Add(e);
g.Children.Add(t);
TextBlock有着更深层的嵌套内容(一些文本和第二个TextBlock)。这个特定的示例变得有点复杂了,因为在这个情况下,XAML编译器将会为子元素自动生成封装对象。如果在文本元素里加入普通的文本,比如TextBlock或Paragraph,它就会自动被封装进一个Run对象中。同样地,子元素FrameworkElement将会被自动地封装进InlineUIContainer元素里。下面的代码对这些进行了解释。
隐式的Run和InlineUIContainer:
<TextBlock>
Name:<TextBlock Text="{Binding Name}"/>
</TextBlock>
显式的Run和InlineUIContainer:
<TextBlock>
<Run Text="Name"/>
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</TextBlock>
以上代码之所以能够运行是因为一对自定义属性。TextBlock通过ContentPropertyAttribute进行了注释,这表示其子元素内容应该加入到其内嵌的属性中。而这个属性属于InlineCollection类型,并且通过一对ContentWrapperAttribute自定义属性进行了注释。
ContentWrapperAttribute自定义属性:
[ContentWrapper(typeof(Run)),ContentWrapper(typeof(InlineUIContainer)),WhitespaceSignificantCollection]
public class InlineCollection:TextElementCollection<Inline>,...
通过以上代码我们就可以发现上例等价于以下代码:
等价的TextBlock代码:
TextBlock t=new TextBlock();
t.Inlines.Add(new Run("Name:"));
TextBlock t2=new TextBlock();
t.Inlines.Add(new InlineUIContainer(t2));
t2.SetBinding(new Binding("Name"));
三,属性和类型转换器:
在上面的例子中我们设置了Window元素的Title属性。这个属性属于String类型。纯文本属性本身就与XML兼容,因为XML是一种基于文本的格式。不过其它属性是怎么样的呢?请看下面代码:
<!--非字符串属性-->
<Rectangle Width="100"
Height="20"
Stroke="Black"
Fill="#80FF40EE"/>
以上设置的属性中没有一个是字符串类型,其中Width和Height两者都是Double型,而Stroke和Fill两者都需要一个Brush。为了支持不同的属性类型,XAML依赖于.NET的TypeConverter(类型转换器)系统。一个TypeConverter可以在不同类型的值之间产生映射,最常见的是在String和原始类型之间。
Width和Height属性是通过LengthConverter类型来进行转换。其中BrushConverter类是为其它两个属性所用,因为尽管它们没有TypeConverterAttribute,但它们属于Brush类型,而Brush类型拥有TypeConverterAttribute,这表示应该使用到BrushConverter.以下代码说明了属性如何在每种情况下进行应用。
public class FrameworkElement:UIElement,...
{
...
[TypeConverter(typeof(LengthConverter))...]
public double Width{...}
[TypeConverter(typeof(LengthConverter))...]
public double Height{...}
}
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush:Animatable,...
由于Stroke设置了一个标准的已命名颜色,因此这将促使相关的SolidColorBrush从Brushes类那里取得。
上面示例等同于以下代码:
Rectangle r = new Rectangle();//创建Rectangle类对象
r.Width = 100.0;//设置属性值(中间经过了LengthConverter转换器转换)
r.Height = 20.0;
r.Stroke = Brushes.Black;
r.Fill = new SolidColorBrush(Color.FromArgb(0x80,0xff,0x40,0xEE));
当然,如果自义定组件允许带有非标准类型的属性通过XAML进行设置的话,它们就会提供自身的类型转换器。
四,属性元素语法:
尽管使用类型转换器系统一般可以通过XAML中的属性来指定属性值,但是它也有一定的限制,比如BrushConverter就不能提供一种渐变填充的方式,对于这些情况,就可以使用XAML所支持的属性元素语法(property element syntax)。
通过属性元素语法,你可以使用嵌套的元素来设置属性,而不直接使用属性。这个嵌套元素的名字形式可以是Parent.PropertyName,其中Parent是作为进行属性设置的元素名称,而PropertyName则是属性名。以下这种缩进式的语法将元素作为属性值进行标记而不是子内容。
属性元素语法:
<Button>
<Button.Background>
<SolidColorBrush Color="#FF4444FF"/>
</Button.Background>
Click me
</Button>
使用属性元素语法来设置一个Button元素的Background属性,它等价于以下代码:
Button btn = new Button();//创建Button类对象
SolidColorBrush brush = new SolidColorBrush();//创建SolidColorBrush类对象
brush.Color = Color.FromArgb(0xFF,0x44,0x44,0xFF);
btn.Background = brush;
btn.Content = "Click me";
这个示例的效果与属性Background="#FF4444FF"一样,也就是说如果将数字颜色指定成一个画刷的话,BrushConverter就会将其转换为一个SolidColorBrush。尽管这种属性元素版本的代码有点冗余,不过它清楚地展示了这里所创建的画刷类型。
尽管这种更详细的语法有助于揭示后台类型转换器的工作原理,但这并不是使用属性元素语法的理由,其主要原因是因为我们需要在属性里嵌套一些更加复杂的定义。如下示例。
嵌套的属性元素:
<Button VerticalAlignment="Center" HorizontalAlignment="Center">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="#800"/>
<GradientStop Offset="0.35" Color="Red"/>
<GradientStop Offset="1" Color="#500"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
Click me
</Button>
XAML效果为:
上例中的等价代码为:
Button b = new Button();//创建Button类对象
b.VerticalAlignment = VerticalAlignment.Center;//设置属性
b.HorizontalAlignment = HorizontalAlignment.Center;
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(0,0);
brush.EndPoint = new Point(0,1);
GradientStop gs = new GradientStop();
gs.Offset = 0;
gs.Color = Color.FromRgb(0x80, 0, 0);
brush.GradientStops.Add(gs);
gs = new GradientStop();
gs.Offset = 0.35;
gs.Color = Colors.Red;
brush.GradientStops.Add(gs);
gs = new GradientStop();
gs.Offset = 1;
gs.Color = Color.FromRgb(0x50,0,0);
brush.GradientStops.Add(gs);
b.Background = brush;
b.Content = "Click me";
五,附加属性:
XAML不仅允许设置普通的.NET属性,而且还支持附加属性。一个附加属性指其属性是由一个不同的类定义的,而不是由进行应用的元素来定义。示例如下。
附加属性:
<Button Grid.Row="1" Name="myButton"/>
其中Grid.Row="1"就是附加属性,附加属性的语法简单易懂,通常总是DefineType.PropertyName的形式,其中DefineType是定义属性的类型名称,而PropertyName则是属性名。XAML会将其解释为对静态DefineType.SetPropertyName方法的调用,并且会传入目标对象和值。以下代码解释了这个过程。
通过代码设置一个附加属性
Grid.SetRow(myButton,1);
以上就是今天总结的内容,下一篇将会总结扩展标记以及其它一些主题,欢迎大家指正,因为以前没有学习过XAML,这次是看了书后总结的,如果大家有一些好的关于XAML方面的资料的话希望可以分享一下。
这一篇我要总结的内容是XAML中的扩展标记(Markup Extensions).
扩展标记
通过类型转换器和属性元素,我们可以将大多数属性初始化为常数值或者固定结构,不过在某些情况下我们需要更强的灵活性。举个例子,虽然我们可能会设置一个等价于某些特定静态属性的属性,但是我们并不知道在编译时该属性值将等于什么,这就像用来表示用户自定义颜色的属性一样。XAML以扩展标记的形式提供一个强大的解决方案。一个扩展标记就是一个在运行时决定如何设置属性值的类。
扩展标记类派生自MarkupExtension,下面代码显示了其非私有成员。这个类定义在System.Windows.Markup命名空间中。
例1:扩展标记类:
public abstract class MarkupExtension
{
protected MarkupExtension() { }//构造函数,初始化从MarkupExtension派生的类的新实例。
public abstract object ProvideValue(IServiceProvider serviceProvider);//在派生类中实现时,返回一个对象,此对象是作为此标记扩展的目标属性的值提供的。
}
下面是一个关于扩展标记的例子。
例2:使用一个扩展标记:
...
<Style TargetType="{x:Type Button}"/>
...
其中Style的TargetType属性有一个用大括号括起来的值,通过这一点XAML编译器就能识别使用了扩展标记。大括里的第一个字符串是扩展标记类名,接下来的内容将会在初始化时传入扩展标记。
在上例中我们使用了x:Type。虽然由XAML命名空间表示的.NET命名空间中没有任何一个叫做Type的类,但是当XAML编译器找不扩展标记类时,它就会为名称添加Extension并且重试。由于有一个TypeExtension类,因此XAML的编译器会使用这个类,并将字符串"Button"传递给它的构造函数,这样就初始化了一个TypeExtension类的实例。然后,它会调用扩展标记的ProvideValue方法来获得一个用于属性的真实值,而TypeExtension则会返回Type对象供Button类使用。
如例1所示,ProvideValue只拥有一个类型为IserviceProvider的参数。这是一个标准的.NET接口,通过它可以提供一套服务,而每一个服务都是一些接口的实现。这里提供了两个服务:IProvideValueTarget和IXamlTypeResolver.通过前者,扩展标记可以获得所有提供值的对象和属性(这里是Style对象的TargetType属性)。通过后者可以将类型名转换成类型(这里是将Type转换成TypeExtension类),并将使用了扩展标记的位置纳入XML命名空间的范畴。TypeExtension就是通过这个服务将其参数转换成Type对象。
XAML的编译器对特定的扩展标记进行了特殊的处理,并在编译时进行求值,由于编译器作者知道那些扩展标记总是返回同样的值给特定的输入。(大多数扩展标记在运行的时候进行求值,其中包括您所写的任何一个自定义扩展名)。TypeExtension就是其中的一个特殊情况,因此,在编译时,编译器将执行等同于例3所示的代码。(由于传递给ProvideValue方法的服务提供者是一个XAML编译器详细的执行过程,因此这里没有显示出来)。
例3:编译时TypeExtension所产生的效果:
TypeExtension te = new TypeExtension("Button");//初始化TypeExtension的实例
object val = te.ProvideValue(serviceProviderImpl);//调用ProvideValue方法,通过IProvideValueTarget接口获得了Style对象的TargetType属性。
例2中TypeExtension类在运行时所产生的效果等同于例4所示的代码。这种特殊的处理其主要原因还是效率的问题,也就是说由于普遍使用了TypeExtension,因此在运行时查找名称将会降低速度。
例4:TypeExtension的运行时效果:
Style s = new Style();
s.TargetType=typeof(Button);
XAML可以通过两种方式将数据传递进扩展标记。一种就是如例2所示的构造函数参数,其中TypeExtension提供一个可以带有字符串的构造函数,并且那个示例将字符串"Button"传进了构造函数。(可以用逗号分隔来传递多个参数)。另一种是设置属性,您可以通过将PropertyName=value对列成一个清单。如例5.
例5:使用带有扩展标记的Name=Value值对:
<TextBlock Text="{Binding Path=SimpleProperty,Mode=OneTime}"/>
传递给扩展标记的属性也与其他所有属性一样,是使用类型转换器来进行解析的。例5等价于例6所示的代码。
例6:为绑定设置属性:
Binding b = new Binding();
b.Path = new PropertyPath("SimpleProperty");
b.Mode = BindingMode.OneTime;
内置的扩展标记
Silverlight提供了许多实用的内置扩展标记。其中一些定义在XAML XML命名空间中,因此按照惯例您可以使用x:前缀来对它们进行访问。下表是常用的扩展标记。
类型 | 说明 |
x:NullExtension | 用来表示空值(编译时进行求值) |
x:TypeExtension | 获得类型对象(在编译时进行求值) |
x:ArraryExtension | 创建一个数组 |
x:StaticExtension | 获得静态属性值 |
下面就来分别介绍这些内置的扩展标记。
NullExtension
NullExtension提供了一种将属性设置为NULL的方法。在某些情况下显示将属性设置为NULL非常重要。比如一个Style可能会将所有元素的Background属性设置为某一个特定值,而您可能会在某一个指定元素上屏蔽这个值。如果想移除背景色,不是将它设置为另一种颜色,那么就要通过显示将该元素的属性的Background设置为NULL来实现。
下面使用NullExtension设置了一个按钮的背景色,这样可以防止按钮的背景色被填充。
<Button Background="{x:Null}">Click</Button>
以上代码等价于:
Button b = new Button();//创建一个Button类对象
b.Background = null;//设置Background属性
b.Content = "Click";//设置Content属性
TypeExtension
TypeExtension会为已命名的类型返回一个System.Type对象。它往往带有一个参数:那就是类型名。在应用了TypeExtension时,TypeExtension会通过IServiceProvider传递给它的ProvideValue方法来获得有关XAML解析环境的信息,并且使它能够像XAML编译器处理元素名称那样来处理类型名。这就意味着,您不必提供一个完全限定的含有.NET命名空间扩展标记的类型名称,相反您只需按以下方式做就可以。
<Style TargetType="{x:Type Button}"/>
TypeExtension将会把字符串"Button"像XAML编译器一样处理成一个类型,也就是说,它将会被包括进默认的XML命名空间中以及任何存在的命名空间映射中。这个扩展标记的作用等价于:
Style s = new Style();
s.TargetType=typeof(Button);
同样,实际应用过程中会复杂很多,不过上面的例子解释了扩展标记的目的和作用。
ArrayExtension
通过ArrayExtension您可以创建一组元素。这个扩展标记有点特殊,因为该标记不需要使用大括号,也就是说要通过一个完整元素来代替。这是因为一个组可以包括多个选项,并且有了ArrayExtension,这些就表现为扩展标记子元素,(不过如果想获得一个空数组,可以使用大括号来实现)。
下例使用ArrayExtension来创建了一个数组作为资源,接着将这一数组作为一个ListBox的数据源。ArrayExtension必须通过其Type属性来指定数组类型,对于这个属性我们可以使用前面介绍的TypeExtension,下面我们来创建一个Brush类型的数组。
<Grid>
<Grid.Resources>
<x:ArrayExtension Type="{x:Type Brush}" x:Key="brushes">
<SolidColorBrush Color="Blue"/>
<LinearGradientBrush StartPoint="0,0" EndPoint="0.8,1.5">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Green" Offset="0"/>
<GradientStop Color="Cyan" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</x:ArrayExtension>
</Grid.Resources>
<ListBox ItemsSource="{StaticResource brushes}" Name="myListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Rectangle Fill="{Binding}" Width="100" Height="40" Margin="2"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
以上代码等价于以下代码:
Brush[] brushes = new Brush[3];//声明Brush类型的数组,大小为3
SolidColorBrush scb = new SolidColorBrush();//创建SolidColorBrush类对象
scb.Color = Colors.Blue;//设置属性Color
brushes[0] = scb;//初始化数组的第一个元素
LinearGradientBrush lgb = new LinearGradientBrush();//创建对象
lgb.StartPoint = new Point(0,0);//设置属性
lgb.EndPoint = new Point(0.8,1.5);
GradientStop gs = new GradientStop();//创建对象
gs.Color = Colors.Green;//设置属性
gs.Offset = 0;
lgb.GradientStops.Add(gs);//调用Add()方法添加对象
gs = new GradientStop();//再创建一个GradientStop对象
gs.Color = Colors.Cyan;
gs.Offset = 1;
lgb.GradientStops.Add(gs);
brushes[1] = lgb;//实始化数组的第二个元素
lgb = new LinearGradientBrush();
lgb.StartPoint = new Point(0,0);
lgb.EndPoint = new Point(0,1);
gs = new GradientStop();
gs.Color = Colors.Black;
gs.Offset = 0;
lgb.GradientStops.Add(gs);
gs = new GradientStop();
gs.Color = Colors.Red;
gs.Offset = 1;
lgb.GradientStops.Add(gs);
brushes[2] = lgb;//实始化数组的第三个元素
myGrid.Resources["brushes"] = brushes;
...
myListBox.ItemsSource=myListBox.Resources["brushes"];
结果如图所示:
注意这个示例可能不是使用标记的最佳选择,不过对于创建一个等价的数组来说,代码还可以编写得更简洁一些。
Brush[] brushes=new Brush[3];
brushes[0] = Brushes.Blue;
brushes[1] = new LinearGradientBrush(Colors.Green,Colors.Cyan,new Point(0,0),new Point(0.8,1.5));
brushes[2] = new LinearGradientBrush(Colors.Black,Colors.Red,new Point(0,0),new Point(0,1));
如果使用标记或代码都可以实现您所要的功能,使用代码往往会更简洁一些,而ArrayExtension存在的主要原因是得益于基于XAML的工具。通过ArrayExtension,这些工具就可以创建一个数组而不需要生成代码。
StaticExtension
通过StaticExtension可以将目标属性设置成指定静态属性或者字段的值。这个扩展标记往往带有一个命名资源属性或者字段的参数,该参数的形式为ClassName.MemberName。下面示例就是使用这个扩展标记获得了SystemColors类其中的一个属性值。
<TextBlock Background="{x:Static SystemColors.ActiveCaptionBrush}" Text="Foo"/>
请注意在实际过程中您往往不会像以上示例那样使用标记。由于它没有适当地与资源系统相结合,因此如果应用程序资源包含一个重载系统画刷的画刷,那这个示例将不会使用那个应用程序级的资源。同样的,也不会在属性发生变化时自动更新属性,也就是说StaticExtension只会获得一次属性值。其作用等价于以下代码。
TextBlock tb = new TextBlock();
tb.Background = SystemColors.ActiveCaptionBrush;
tb.Text = "Foo";
Code Behind(隐藏代码文件)
在前已经讲过,XAML是支持隐藏代码(Code Behind)这种理念,所谓隐藏代码理念,就是指界面和行为分开处理,其中XAML文件用来定义用户界面,而隐藏代码文件提供行为。
XAML是通过命名用partial类(分部类,前面已经讲过)来支持隐藏代码。通过partial类,一个类定义就可以在多个源文件中使用,而每一个单独的文件只包含一个partial类定义。在编译时,编译器会将这些结合起来组成一个完整的类定义。partial类的主要目的是允许生成的代码和手写的代码共用一个类而不必共用一个源文件。
如果您已经在XAML中定义了一个元素,并且想要在隐藏代码中进行使用,只需要设置其Name属性(类似于ASP.NET中的ID),如下例。
例1:已命名的元素
<Button Name="myButton">Click</Button>
通过以上标记,XAML编译器将会给类添加一个myButton的字段,并且会在初始化时进行设置以引用这个按钮,这样就可以在隐藏代码中编写代码来使用这个元素。如。
例2:从隐藏代码文件中使用已命名的元素
myButton.Background = Brushes.Green;
注意,并不是所有的类型都有一个Name属性,不过,你可以通过一个x:Name属性来代替隐藏代码为它们生成一个字段,并且将x:作为XAML命名空间的前缀,如。
例3:x:Name属性
<Button Content="Click">
<Button.Background>
<SolidColorBrush x:Name="bgBrush" Color="Yellow"/>
</Button.Background>
</Button>
在隐藏代码中使用该元素,在这里更改为元素的Color属性的值,如:
bgBrush.Color = Colors.Red;
那为什么会这样呢?事实上,你甚至可以在Name合法元素上使用x:Name样式,例如将例1中的Name替换成x:Name也不会改变它的行为。这是因为FrameworkElement将Name属性识别成x:Name属性的映射。这可以通过RuntimeNamePropertyAttribute来实现,如下例。
例4:将Name属性映射到x:Name
[RuntimeNamePropertyAttribute("Name"),...]
public class FrameworkElement : UIElement, ...
如果类型进行了这样的属性注释,那么在XAML中已命名的属性和x:Name之间就可以互相转换。但是如果类型中没有这种自定义属性的话,您就必须通过XAML中的x:Name属性来生成隐藏代码中的字段,即使目标类型有一个Name属性。
隐藏代码的其中一个主要任务就是制定应用程序响应用户输入的功能,因此,需要经常在XAML中把事件处理函数附加到元素上。首先的方法就是编写在隐藏代码初始化的过程中附加处理函数。如下示例。
<Button x:Name="myButton" Click="myButton_Click">Click</Button>
以上标记等价于在隐藏代码中附加处理函数,如下代码。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myButton.Click += myButton_Click;
}
}
尽管以上代码看起来与属性语法类似,不过XAML编译器将识别Click成员是一个事件,而不是一个属性。它会认为隐藏代码中提供了一个叫做myButton_Click的函数,并且它会将函数作为按钮Click的事件处理进行添加。但是,你必须确保函数有正确的签名,也就是说所有.NET事件都会有一个特定类型的函数签名。