XAML那些事儿
这是来北京第二篇随笔。
很久没有写Silverlight的文章了,如果你从我只有十几篇的随笔中找出可怜的2,3篇Silverlight的文章,你会发现那已经是几个月之前的事情了。我其实比较低产,所以关注我其实没什么好处,我仅仅只能向我的粉丝们保证:这里的内容,其他地儿没有!
这个标题其实也在Live Writer里放了很久。直到前几天,看到Alexis在闪存中回复说苏鹏老师的Silverlight视频中讲到我的一篇Silverlight的文章,才突然来了点兴致,于是决定把这篇随笔在找到工作之前完成,因为一旦开始新工作可能会比较忙没时间写。
XAML在Silverlight或WPF的开发中,初学者会认为很简单:不就是设计布局的吗,我把控件拖到适当的位置,然后了解一下数据绑定就可以做项目了。
哎呀,咱们太有缘分了,我也是这么开始学习的耶!
可是话是这么说,嘿,朋友,问你几个问题:
1. 为什么在XAML中还可以声明一个非UI对象,它不是专用来布局的吗
2. 怎样在some.xaml.cs中引用style.xaml文件中的对象呢,我没有在some.xaml中引用style.xaml
3. 郁闷,ListBox中绑定的数据在设计器中根本看不到效果,每次都要实际运行才能看到布局效果
其实,这一切都是XAML的问题。深入了解XAML会对学习Silverlight的过程大有益处。
1. 从绘图说起
系统有绘图的API,因此运行在之上的应用程序能够调用相关API绘制出应用程序需要的图形。
而对于应用程序设计人员来说,你必须告诉应用程序中绘图的方法,你想要绘制一个什么样的图形,在什么位置。
我们通常用一个类来表示某个图形结构,这样便于抽象出一些常用的图形结构和功能,比如TextBox,Button等等都是代表不同图形结构和功能的对象。而为了区别于一般对象,也为了让绘制图形的方法更好的区别对象,通常具有UI特性的对象必须是一些固定类的子类,比如Silverlight中的UIElement。
因此,这里注意一点,XAML并不是用来布局或者绘制UI的,我们后面看到,它仅是一种声明对象的方法。这里我们不需要借助XAML,我们一样可以绘制UI,试试显示这个UserControl:
public class NoXamlControl : UserControl
{
public NoXamlControl()
{
Rectangle rect = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Red),
Width = 50,
Height = 50
};Grid grid = new Grid();
grid.Children.Add(rect);
this.Content = grid;
}
}
我们看到,只需要声明一定的UI对象(代表一定的图形结构和位置),就可以绘制UI。
2. 声明式语言
然而你可以看到上面的绘制UI的方式,如果我们整个页面都是这样的方式去绘制UI,那么会有以下问题:
1. 完全设计时看不到效果,必须运行才能看到
2. 大量的对象构造代码,没有结构,不便于维护,比如你想修改某个控件,可能不能很快找到这个对象在什么地方构造的
3. UI设计人员没有工作了
XAML是一种声明式语言,它其实就是一个XML文件。因为XML的结构很容易用来表示一个对象:
1. XML中的Property或者子节点对应对象的属性
2. XML中的Property的值或者节点的Value对应某个属性的值
3. 子节点可以是另一个对象的引用
所以,XML很自然被用于声明对象,而且运用XML声明对象可以有以下好处:
1. 具有清晰的层次结构,很容易用于表示UI对象,因为UI对象其实很容易表示成一棵树,只要添加一个容器的概念
2. 便于修改,很容易定位到一个需要修改的对象
3. 可以让UI设计人员和业务开发人员分开
因此,XML被大量用于声明对象,这不需要我们通过代码来构造对象。
3. 应用程序怎样结合声明式语言
那声明的对象怎么被用在程序中呢,因为程序在运行的时候需要使用这些对象(当然这是不好的设计模式,我们看到MVC中,其实View中声明的对象就不会被我们的程序使用,但还是会被框架使用)。
所以,一般有一个加载声明的对象的方法,我们看UserControl的构造函数(在InitializeComponent()方法上右键—》Go To Definition):
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Windows.Application.LoadComponent(this, new System.Uri("/SilverlightApplication2;component/MainPage.xaml", System.UriKind.Relative));
this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
}
这里的LoadComponment方法其实就是加载构造XAML的对象树,它可以将制定的XAML文件构造成一个该XAML文件所表示的对象,除非这个XAML文件格式错误。所以一个名为style.xaml的XAML文件:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"></ResourceDictionary>
我们可以在程序中这样加载:
ResourceDictionary dict = new ResourceDictionary();
System.Windows.Application.LoadComponent(dict, new System.Uri("/SilverlightApplication2;component/style.xaml", System.UriKind.Relative));
这样,Silverlight就可以和声明式语言结合起来。其实所有支持声明式语言的程序都是这样的,有一个XML文件来声明对象,有一个加载器来构造XML中的对象。
从这里我们可以看出,声明式语言的作用主要在于以声明的方式创建对象,而不仅仅是布局,所以在XAML中我们同样可以创建非UI的对象。例如添加一个string的对象或者其他自定义的非UI对象。
4. 声明式语言用在IDE中--视图设计器(工作原理)
声明式语言其实有很多用处,比如你可以在配置文件中配置某些值,而不是将对象的构造放在程序内部,而不可修改。
当然,我们最熟悉的却是视图设计器。
需要复杂UI的设计需要我们能在设计时看到运行时的效果,并且需要将UI设计部分从程序抽取出来。而我们看到UI对象非常适合用声明式语言来构造,于是这样的方式很快被用于视图设计器。
视图设计器的工作原理:其实就是将XML中声明的UI对象按照UI对象表示的结构模拟运行时立即显示出来。它一般有一个单独的方法构造声明的对象树,然后将按照UI特性立即显示给开发者。所以,一般设计器有两种视图:XAML和Design。
所以,这里只要XAML表示的是一个UI元素,那么它就可以被呈现,而非UI的资源文件就不会被呈现,我们看下面代码:
<UserControl
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"><Grid x:Name="LayoutRoot" Background="White">
<Rectangle Fill="Red" Height="100" Width="100"/>
</Grid>
</UserControl>
如果你足够细心,你会发现这仅仅是一个XAML文件,它没有对应的.xaml.cs隐藏文件,你切换到Design视图,仍然是能够被呈现的。只要是UI元素,就可以在设计时被呈现。
那想想如果XAML中代表的是特定的对象结构和另一个设计时模拟器,这个Design是不是还可以有特殊的呈现方式呢,没错,WF。所以我们知道WF可以使用XAML来设计。
这里我们也可以了解设计时模拟器和真实运行时的区别:
1. 设计时XAML对象树是由设计时模拟器加载的,是我们无法控制的,而在运行时是可以修改的对象属性的
2. 设计时只需要XAML文件即可
3. 设计时无法执行事件,及其他方法,因此不会调用数据库之类获取数据,所以如果ListBox的数据需要来自数据库,将无法看到设计时效果
5. Silverlight加载XAML对象树
那么Silverlight其实到底是怎么加载XAML对象树的呢,不管是设计时模拟器的调用还是运行时的调用,这里其实都是一样的。
XAML是一个XML文件,它表示的是一个对象。因此Silverlight加载器会根据对象类型创建这个对象树,起创建方式是从上至下顺序创建,在遇到一个对象类型的时候,调用类型的公共无参构造函数。
因此:
1. 任何具有公共无参构造函数的类型都可以声明在XAML对象树中被构造。所以我们说XAML是另一种声明对象的方式
2. 如果一个元素需要引用另一个元素的属性值,另一个元素必须声明在这个元素的前面,所以我们通常将资源Resources声明在前面
3. 在构造对象时,公共无参构造函数将被执行
因此,如果有一个UserControl,它具有这样的构造函数:
public partial class SilverlightControl1 : UserControl
{
public SilverlightControl1()
{
InitializeComponent();
MessageBox.Show("Hello");
}
}
则你在设计时能看到这个弹出的Hello的消息。
Silverlight就是这样构造对象树的。
6. Visual Studio和Expression Blend设计时分析
那么我们要怎样在设计时能够看到效果呢,因为往往很多数据都是来自数据库。
如果看我以前关于Blend的设计教程,我里面提到的一点就是示例数据,也就是本地数据。其实现在我们很容易理解,就是将资源文件添加到XAML中,后面就可以引用,所以Blend设计一定需要这样的示例的模拟支持。
另外值得注意的是,在Blend中,XAML不仅仅是设计静态布局,还具备设计行为的能力。这其实主要是归功于Behavior,VisualState等相关对象的设计,可以参看我这方面详细介绍的文章。
但是,为什么XAML还可以做这么神奇的事情,其实原因也很简单,它可以调用构造函数,它的设计时模拟器可以根据相关类型呈现不同的视图给你,比如编辑各种模板,编辑动画,等等。这些我相信你想得通。
7. 总结
好了,XAML的介绍到此为止!
自上一篇Silverlight的文章,已经有几个月了,虽然低产,但是我希望每篇文章都会给你不一样的收获。也许能让你真正的理解一些东西,做到举一反三,也许我还没没有做到这个目标,读者自己去评价吧,我只能做到这样了,呵呵。
虽然只有几篇,其实每一篇的内容都是精心挑选,精心设计写作思路的,这里再将地址整理一下。
Silverlight系列文章:
Behavior:http://www.cnblogs.com/hielvis/archive/2010/10/06/1806813.html
Silverlight布局:http://www.cnblogs.com/hielvis/archive/2010/10/12/1847025.html
Blend模板设计:http://www.cnblogs.com/hielvis/archive/2010/10/21/1857415.html
XAML那些事儿:http://www.cnblogs.com/hielvis/archive/2011/02/27/1966201.html
Blend 4:http://www.cnblogs.com/hielvis/archive/2010/10/09/1846046.html
再加上下一篇:《依赖属性那些事儿》,Silverlight系列就到此结束,应该是我想表达的一个特别的系列,对不起没有太多简单的教程,我希望让你去理解一些东西,如果你不愿意去理解,那就不是我的事了,呵呵。
有什么问题,可以留言,欢迎讨论!