[转]Silverlight布局管理
原文出处:国双科技-黄勇坚 Silverlight布局管理
1. 布局管理介绍
复杂的页面、软件界面都是由若干个界面部件组成的。每个部件占据着屏幕的一个部分。如何将这些部件有机地放在用户界面中,并且适合种尺寸大小的显示,成为界面设计者的一大难题。
在界面的大小改变时,界面中的控件的位置和大小均会发生变化,如有的控件自动产生滚动条,有些控件之间的距离变小或者变大,有的控件的宽度和高度变小,有的控件大小不变但是即因为父容器的大小不足以容纳子控件而导致只显示一部分……如果要设计者考虑如何摆放这些控件,会降低程序人员和设计者的生产率。由于所有的软件都涉及到界面总局问题,因为寻求一种自动处理的布局管理方案则越来越迫切。在设计网页时,往往根据固定的尺寸进行网页元素的布局;但是在设计软件界面时,软件窗口的大小往往是可变的,如果仅使用XY坐标进行定位,会导致界面在不同窗口大小显示时产生较大的差异,甚至会发生显示异常而影响用户体验。
理想的布局方案应该做到:
1. 当界面大小发生变化时,布局方案自动根据布局约束调整界面,而不需要开发人员编写界面大小变化的处理逻辑。
2. 内置一系列的布局方案,支持常用的布局,包括表格布局,顺序布局,坐标布局。
3. 布局方案可扩展,当内置的布局不满足开发者要求时,开发者可以扩展布局方案。
4. 支持裁剪,可以指定超出特定范围的内容不予展示。
2. 布局管理的一般方法
处理布局管理,一般有以下的两种方法:
a) 处理窗口大小改变的事件
这是传统的方法,当没有布局管理器时,往往使用这种方法。例如在使用Win32 API开发的应用程序中,Win32平台并没有提供布局管理器,这时程序员需要处理父窗口的窗口大小改变的事件通知,在事件通知处理函数中一一对子窗口的大小和位置进行计算和调整,从而使子窗口也产生窗口大小改变的事件消息,程序员继续处理子窗口的窗口大小变化的消息,再对子窗口中的下一级子窗口的大小和位置进行计算和调整……一直递归到最底层的子窗口,或者对该窗口的子窗口的布局不感兴趣的窗口。
这种方式实质将计算窗口大小、根据需要排列窗口的任务交给了开发人员,开发人员负担很大。但是通过处理窗口大小改变的事件通知,却给了开发人员极大的灵活性。
b) 使用布局管理器
这是当前的软件平台中流行的解决方案。Java Swing、WPF、Silverlight均提供了布局管理器。只需要选择适当的布局管理器,把控件加入布局管理器中,布局管理器会根据开发人员所设置的约束性条件自动调整控件的位置和大小。例如Java提供了FlowLayout、BorderLayout、GridLayout,在FlowLayout中的控件会按顺序依次排列,在BorderLayout中把容器划为上下左右中的五块区域并把控件根据开发人员的设置放到指定的区域,在GridLayout中可以采用表格布局。Silverlight的Canvas、StackPanel、Grid提供了相似的布局支持。
使用布局管理器,共同点是开发人员设置好布局方式和约束条件时,则不需要处理界面大小变化的事件通知即能实现控件自动布局。
3. Silverlight布局管理
以下的描述针对于Silverlight 2.0。未来的Silverlight版本可能会在细节上有所调整。
3.1. Silverlight插件外布局和Silverlight插件内布局
Windows平台下的Silverlight是一个普通的ActiveX控件,可以放置于任何的ActiveX容器中。但是主要的应用还是发生在浏览器上,即由浏览器作为ActiveX容器。Silverlight内容的布局就包括Silverlight插件在浏览器中与其它HTML元素的布局以及Silverlight插件内的Silverlight内容布局。Silverlight插件与其它HTML元素的布局不在本文的讨论范围之内,Silverlight插件作为网页中的一个部分和其它HTML元素一起使用CSS或者TABLE进行定位。如果Silverlight内容的大小超过插件的大小,则只能显示一部分的Silverlight内容。本文只描述Silverlight插件内的布局。
3.2. Silerlight布局的LayoutSlot和LayoutClip
每个元素外围都有一个称为Element Bounding Box的虚拟的矩形区域,这个区域可以通过LayoutInformation类中的GetLayoutSlot()的方法获取,本文将这个Element Bounding Box称为LayoutSlot。
上图虚线部分即是Child Element的Bounding Box(LayoutSlot)。LayoutSlot的大小是由系统决定的。
只会位于Clip区域中的内容才会被显示在屏幕上。
3.3. Silverlight布局流程
Silverlight的布局流程,主要分为两大步:Measure和Arrange。UIElement定义了Measure方法和Arrange方法。在Silverlight中,所有涉及界面展示的类均从UIElement中派生。即Silverlight所有在界面显示的元素均支持Measure和Arrange。
先看看Measure和Arrange方法的c#签名:
public void Measure(
Size availableSize
)
public void Arrange(
Rect finalRect
)
Measure方法是在容器(即父元素)在为子元素进行布局处理时,向子元素询问:“我给你的大小是availableSize这么大,你自己算算在这基础上究竟想占多少的空间吧。”于是容器调用子元素的Measure()方法,并且传入availableSize的参数以指定可用的大小,可用的大小完全由容器根据它内部子元素的个数、排列方式、每个子元素的与Layout相关的属性的设置等因素自主决定,与具体的容器的实现相关。
当子元素计算大小时,会调用自身的MeasureOverride()方法,这是一个虚方法,因为可以被子类覆盖:
protected virtual Size MeasureOverride(
Size availableSize
)
这时开发人员可以覆盖这个方法,从而影响子窗口的Measure的结果。这个方法的返回值则是子窗口根据自己的内容认为自己的最佳大小。这个子元素单方面认为的最佳值不能被开发者所读取,Silverlight没有提供属性和方法可以获取这个值。
接着,Silverlight Runtime根据子元素影响布局的一系列属性(如Width、Height、Margin之类的属性)在子元素认为的最佳大小的基础上进行计算,从而计算出该子元素的DesiredSize。DesiredSize是UIElement的属性,但是仅定义了“get”方法,而没有定义“set”方法。所以DesiredSize的设置是由Silverlight Runtime进行的。这时Measure()方法返回,程序执行控制权交还给父元素。调用一个元素的Measure()方法,最重要的作用就是使该元素的DesiredSize属性有效。注意刚才提到的元素单方面认为的最佳大小并不一定是DesiredSize,DesiredSize的大小是由Silverlight Runtime根据元素单方面认为的最佳大小和该元素的一系列Layout属性计算确定的,外界不能直接干预DesiredSize的计算(Silverlight没有提供这方面的接口)。其实仔细一想,发觉这种处理是理所当然的,否则如果还需要程序员根据Margin、Width、Height、MinHeight、MaxWidth、VerticalAlignment……等一堆的影响Layout的参数去计算DesiredSize,而不是系统自动算,这岂不是会累死?可见DesiredSize是包含Margin的大小的。
DesiredSize被计算出之后,Measure()过程完毕。DesiredSize的计算是Measure过程完毕的理程碑。DesiredSize的计算也是Measure()的目的。
举个例子:
<Grid>
<Button Margin="50" Width="100" Height="40" Margin="50" Content="MyTestButton"></Button>
</Grid>
Grid会在它的MesureOverride的方法中,调用Button的Measure()方法,Silverlight Runtime再调用Button的MeasureOverride()方法让Button决定自己的最佳大小,这时Button在 MeasureOverride()返回基于内容的最佳Size(82,22),Silverlight Runtime根据Button的Width、Height和Margin确定该Button的DesiredSize为Size(200,140)。
Measure过程完毕后,开始进入Arrange阶段。
Silverlight Runtime会调用容器的Arrange()方法,设置容器的实际位置和大小。Arrange()方法的传入参数是一个Rect,而Measure()方法的传入参数是一个Size,可见Arrange()方法起到改变元素的大小和相对父元素的位置。Arrange()方法不是虚方法,开发人员无法覆盖,但是之后Silverlight Runtime会在调用Arrange()方法中再调用元素的ArrangeOverride()方法,这是一个虚方法,允许开发人员覆盖这个方法在Arrange阶段提供自定义的行为。
在ArrangeOverride()方法中,一般都要逐个调用子元素的Arrange()方法,然后Silverlight又会自动调用每个子元素的ArrangeOverride()方法,递归进行。调用Arrange()方法中的Rect参数的值是由父元素自己决定的,既可以参考子元素的DesiredSize,也可以完全无视子元素的DesiredSize,完全取决于实现者在ArrangeOverride()方法中所采用的策略。当ArrangeOverride返回后,Silverlight Runtime设置该元素的Layout Slot和Layout Clip信息,这时通过LayoutInformation类则可以获取Layout Slot和Layout Clip信息,并且该元素的ActualHeight、ActualWidth、RenderSize也可用。而在这之前,是不能通过LayoutInformation类获取Layout Slot和Layout Clip的信息的,ActualHeight、ActualWidth、RenderSize这类只有get方法的属性也是不可用的。
接着,如果元素的大小(即Actual Height和Actual Width有改变)发生了变化,元素的SizeChanged事件被触发,这时元素其实已经完成了对子元素的布局行为。并非所有的对Layout的更新都是因为元素大小改变而发起的,因为一次Alignment的改变,Margin的改变以及一次显式的UIElement.UpdateLayout()的调用均会导致Layout的更新。
最后,元素的LayoutUpdated事件被触发。整个布局管理的流程完成。
总结一下整个过程:
图 Silverlight布局管理总体流程
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。