《Inside XAML》翻译半成品
时间仓促,胡乱翻译了一通,并没有完全按照原文去翻译,我是照我的理解来的。不明白只处请参照原文。
什么是XAML?
XAML的全称是eXtensible Application Markup Language,用于像HTML构建WEB页面一样构建应用程序的界面——当然,同样也可以构建WEB页面,只要使用不同的编译器就可以。Longhorn的系统界面就是使用XAML构建的。
XAML的"Hello World"
<Text>Hello World</Text>
<Button>Click me!</Button>
</FlowPanel>
以上代码表示在页面上以“流”的方式显示一段文本和一个按钮。其效果如下图:
为什么需要一种新的标记语言呢?
微软设计了这种新的标记语言,而没有使用原有的HTML或SVG(Scalable Vector Graphics -- 一种基于XML的强大的图形系统)。其主要原因是:
1、HTML主要是用于WEB上的,而XAML则可以用到应用程序上。
2、XAML与运行时(runtime)有者很大的关联,而SVG则没有。XAML是被设计来直接集成在WinFX上的。
XAML与对象
每一个XAML中的元素在运行时里都对应一种对象,拿上面的例子来说,程序在运行的时候就对应三种对象:FlowPanel, Text, 和 Button。这些对象都是Avalon类库种的一部分——XML命名空间http://schemas.microsoft.com/2003/xaml 已经告诉了我们类的来源。当然,你也可以自己定义自己的类。甚至可以用XAML写出控制台应用程序。
通常,运行时对XAML文件的处理时编译而不是解析。当你编译一个基于XAML的项目的时候,编译器会为每一个XAML文件生成一个类,那些类包含了一些代码,用来生成在XAML文件中指定的对象。如果希望看到XAML生成的代码,可以使用Longhorn下的Visual Studio .NET Whidbey(具体过程不在此阐述,请查看原文)。
打开生成的代码文件,可以看到一个类的定义。对于每一个XAML文件来说,编译器将只会生成一个类,而且此类将继承自XAML根元素所描述的类。例如,对于上面所说的那个XAML文件,它的根元素是FlowPanel,生成的代码就是:
{
// 创建主面板
MSAvalon.Windows.Controls.FlowPanel _FlowPanel_1_ = this;
((MSAvalon.Windows.Serialization.ILoaded)(_FlowPanel_1_))
.DeferLoad();
// 创建Text
MSAvalon.Windows.Controls.Text _Text_2_ =
new MSAvalon.Windows.Controls.Text();
((MSAvalon.Windows.Serialization.ILoaded)(_Text_2_))
.DeferLoad();
// 把Text设置为FlowPanel的子结点
((MSAvalon.Windows.Serialization.IAddChild)(_FlowPanel_1_))
.AddChild(_Text_2_);
((MSAvalon.Windows.Serialization.IAddChild)(_Text_2_))
.AddText("Hello World");
((MSAvalon.Windows.Serialization.ILoaded)(_Text_2_))
.EndDeferLoad();
// 创建Button
MSAvalon.Windows.Controls.Button _Button_3_ =
new MSAvalon.Windows.Controls.Button();
((MSAvalon.Windows.Serialization.ILoaded)(_Button_3_))
.DeferLoad();
((MSAvalon.Windows.Serialization.IAddChild)(_FlowPanel_1_))
.AddChild(_Button_3_);
((MSAvalon.Windows.Serialization.IAddChild)(_Button_3_))
.AddText("Click me!");
((MSAvalon.Windows.Serialization.ILoaded)(_Button_3_))
.EndDeferLoad();
((MSAvalon.Windows.Serialization.ILoaded)(_FlowPanel_1_))
.EndDeferLoad();
}
所有代码都是在编译期被生成的,也就是说,在运行期XAML文件是用不着的。如果你想动态地生成用户界面,没有必要通过编译XAML来实现,你可以使用WinFX提供的API来把未被编译的XAML直接转换成对象。
属性
就像创建对象一样,XAML也允许为对象设置属性。例如,我们可以这样修改Button元素:
new MSAvalon.Windows.Media.SolidColorBrush(
MSAvalon.Windows.Media.Color.FromARGB(255, 255, 0, 0)
);
如果想知道XAML编译器是怎么把Red转换成以上代码的,你可以使用.Net一直在使用的技术:类型转换。类型转换是.Net设计时环境的一部分,它用来对显示在VS.Net属性窗口中的属性值和对象属性的实际属性进行相互转换。类型转换也可以生成代码来初始化对象的属性——在Windows Forms应用程序中,VS.Net在InitializeComponent方法中使用他们来创建代码。同样,XAML编译器使用类型转换技术来把属性字符串转换成初始化代码。
复杂属性
并不是所有属性都可以使用字符串来表示,有些属性是由一系列嵌套的对象组织起来的。XAML支持一种被叫做“复杂属性”的特殊语法,使用它可以通过设置子元素——而不是属性字符串——来设置对象的属性,这个时候,XAML元素表示一个复杂属性,而不是子对象。表示复杂属性的元素必须有一定的规则:它必须是父元素所表示对象的一个属性的名称。例如:
<Button.Background>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="Red" Offset="0" />
<GradientStop Color="Magenta" Offset="0.25"/>
<GradientStop Color="Blue" Offset="0.5"/>
<GradientStop Color="White" Offset="1"/>
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
Click me!
</Button>
这个例子并不是想一般的类型转换机制那样把字符串转换成一个“brush”,而实际上我们通过标记创建了一个“复杂的brush”——LinearGradientBrush ,同时,还创建了很多的填充区(fill stages)。这个例子创建了两个复杂属性,他们一个嵌套着一个。<Button.Background>元素设置了Button的背景属性,在它的内部,<LinearGradientBrush.GradientStops>元素设置了“linear gradient brush”的GradientStops 属性。复杂属性的语法工作方式与其他的XAML元素一样——它也允许构建对象树,唯一不同的是,他们将会被指定为元素的属性而不是子元素。显示的结果是:
添加代码
如我们所知,XAML编译器为每一个XAML文件生成一个派生自根元素类型的类,并且包含创建所有子元素的代码。然而,只生成这些代码还不足以构建一个UI,用户界面不仅是要展示信息,也必须能够响应用户的输入。因此,通常都要添加一些代码来提供UI的行为。
把代码写进XAML文件是可以实现的。你可以通过Definition 命名空间中的<Code> 元素,把代码直接写在里面,XAML编译器将会把它添加到生成的代码中去。添加代码时,应该把代码写到CDATA 段中去,例如:
xmlns:def="Definition">
<Text>Hello World</Text>
<Button>Click me!</Button>
<def:Code>
<![CDATA[
// Will be added to generated source file
public string Hello()
{
return "Hello!";
}
]]>
</def:Code>
</FlowPanel>
然而,做过动态WEB页面的人都知道,把代码和标记都放到一个文件中时极其不易维护的。最好就是把UI的描述和响应UI动作的代码分割开来。使用一种与“code-behind”很像的技术就可以实现代码和标记的分离——利用partial 关键字(partial关键字的介绍请参考C# 2.0规范)。于是,上面的代码就可以写成:
{
public string Hello()
{
return "Hello!";
}
}
当你用Visual Studio .NET创建一个XAML项目时,每个XAML文件会自动产生一个与之相对应的partial代码文件,在这个文件中添加的代码将会在编译XAML的时候自动添加到XAML的代码中。
当你为一个XAML文件写代码的时候,你也希望可以对标记描述的对象进行操作,以便控制它。这时候,你需要给标记中描述的对象加上一个ID属性:
完成上面的工作以后,XAML编译器将会把对象的ID设置成上面所指定的ID。例如,上面的例子就可以这样使用:
要写的代码并不经常是上面所说的那种,而是那些复杂的事件处理。
事件处理
添加代码最主要的原因是进行事件处理,处理那些来自用户,或是来自UI本身的事件。我们可以通过简单的添加属性字段来描述对象要处理的事件,例如,让Button处理Click事件:
这将使编译器生成这样一段代码:
new MSAvalon.Windows.Controls.ClickEventHandler(this.OnClick);
为了让他通过编译,我们必须在代码文件中写一个相应的OnClick方法,Button使用的delegate是ClickEventHandler ,所以,我们写的方法应该有相应的签名:
{
textElem.TextRange.Text = "Foo";
}
结论
XAML是一种简单但很强大的构建.Net对象树的方法。因为XAML基于XML,所以我们可以直接基于XAML来创建程序。这不仅仅使手工创建UI很容易,这也是与生成XAML的工具直接相关的——未来的设计工具也将能过使用XAML格式导出文档或绘制界面,也将会使通过XSLT把XML数据转换到XAML中变得容易。XAML使用户界面设计清晰的从代码中分离出来,而且,XAML被集成到WinFX中,这使得使用代码操作那些定义在文件中的元素很容易。