对于程序开发来说,创建一个友好的用户界面,提供良好的用户体验相当重要。因此,如何有效组织、布局信息,对于像Sliverlight这样,要在不同浏览器如IE、Firefox,不同显示器,不同操作系统的环境下运行的程序来说,尤为重要。
Silverlight提供了多种布局控件,例如StackPanel、WrapPanel、DockPanel、Grid和Canvas等。通过混合运用这些不同的而已控件,开发人员可以构建出各种不各样的内容布局页面。
如图所示,在Silverlight中,所有这些而已控件,均继承自Panel类。而Panel类,扩展了UIElement类的Background和Children属性。
我们可以把这些布局控件看作是布局的容器。从XAML可以看到,一个Silverlight窗口仅可以容纳一个元素,那么,要在页面上添加纷繁的控件以表达程序的内容,就需要通过容器,存放更多的其它控件。
让我们一个一个来熟悉Silverlight为我们提供的这些“容器”。
l StackPanel
把控件件栈一样,以横向或纵向的方式堆积起来;
l WrapPanel
以横向或纵向为主向,排布控件。当主方向已经不能再排布更多的控件时,在另一个方向上添加一个单位。例如,当以横向为主向时,横向已经排满控件时,控件就“流向”下一行——类似于ASP.NET的ListView。此控件在Silverlight Toolkit中提供。
l DockPanel
靠边排布控件,此控件在Silverlight Toolkit中提供。
l Grid
强大的布局控件,把界面划分成若干个行和列的隐形表格,把控件排布在不同的行与列中。Grid几乎是最常用的布局控件,以至于用Visual Studio创建Silverlight项目时就默认提供了一个Grid作为布局控件。
l Canvas
允许控件以固定坐标出现在页面上的控件。用此控件布局最省力,但是,扩展能力也最差。
StackPanel
首先来看StackPanel。默认情况下,当我们往StackPanel里添加控件的时候,控件会垂直堆叠。例如:
<Button Content="Button1" />
<Button Content="Button2" />
<Button Content="Button3" />
</StackPanel>
就会生成一个垂直堆叠着三个按钮的界面:
此时,由于是垂直堆放,垂直方向上,按钮的高度是其可以显示的最小时,而水平方向上,则拉申至铺满整个容器。
有时候,我们需要水平方向上堆放控件,我们可以通过设置StackPanel的Orientation属性来实现横向排布。
<Button Content="Button1" />
<Button Content="Button2" />
<Button Content="Button3" />
</StackPanel>
得到效果如下:
对于堆放于容器中的控件而言,也会有一些布局相关的属性会影响到最终的布局效果。
l Alignment
当控件堆放在一个Orientation为Horizontal的StackPanel时,由于其水平方为控件堆叠方向,所以,HorizontalAlignment不会有效果。但是,如果用户设置了VerticalAlignment,控件垂直方向上主不再是拉申至整个容器,而会变成恰好能容纳文字的高度。例如:
<Button Content="Button1" VerticalAlignment="Top" />
<Button Content="Button2" VerticalAlignment="Bottom" />
<Button Content="Button3" VerticalAlignment="Center" />
</StackPanel>
我们将得到三个按钮,从左到右依次为上对齐、下对齐和居中。
l Margin
Margin控制控件的外边界。当一个控件的外边界为统一的数值的时候,我们可以只设置一个值;否则,按照左、上、右、下的顺序,以逗号隔开,依次设置。
例如,Margin="5",我们将得到上下左右均为5的外边界;而如果设置Margin="5,10,20,30",那么,左外边界为5,上外边界为10,右外边界为20,下外边界为30。这个与CSS中的上、右、下、左的顺序稍有差别。
来看一个具体的例子,我们给第二个按钮添加左右外边界:
<Button Content="Button1"/>
<Button Content="Button2" Margin="10,0,50,0"/>
<Button Content="Button3"/>
</StackPanel>
得到结果如图:
当然,也可以对Button2设置上下外边距。
l MinWidth和MinHeight
这个比较容易理解,控件的最小宽度(高度),当界面空间小于控件的最小宽度(高度)时,控件将被裁掉一部分。
l MaxWidth和MaxHeight
设置控件的最大宽度(高度),当控件内容膨胀到超过控件的最大值时,控件内容将被截断;另外,即使设置了控件的HorizontalAlignment或VerticalAlignment为拉申,控件均不会超过其最大值。
WrapPanel
StackPanel适用于在同一个方向上堆放控件。WrapPanel同样也在一个方向上堆放控件,但是,如果这个方向上不够堆放了,那么,自动的执行换行操作。
由于WrapPanel和DockPanel是通过Sliverlight Toolkit提供的,因此,要使用它们,我们需要首先安装Silverlight Tookit,可以通过http://www.codeplex.com/Silverlight下载Silverlight Toolkit并安装。
安装好了以后,我们需要通过添加控件窗口,把它添加到工具箱里,之后我们就可以添加一个WrapPanel了。
它跟StackPanel一样,有一个Orientation属性,用来设置排布方向,默认值为Horizontal。让我们把五个按钮放在一个WrapPanel里,然后,调整IE的大小,来看看会是什么效果。
<Button Content="Button 1" />
<Button Content="Button 2" />
<Button Content="Button 3" />
<Button Content="Button 4" />
<Button Content="Button 5" />
</controlsToolkit:WrapPanel>
效果如下,当横向宽度不够时,控件会在纵向添加一个行,继续横向的排布所有的控件。
DockPanel
DockPanel使内部的控件附着在自己的边缘。它跟WrapPanel一样,通过Silverlight Toolkit提供。
我们可以通过AttachProperty,设置控件附着在哪一个边缘,例如:
<Button Content="Top Button" controlsToolkit:DockPanel.Dock="Top" />
<Button Content="Bottom Button" controlsToolkit:DockPanel.Dock="Bottom" />
<Button Content="Left Button" controlsToolkit:DockPanel.Dock="Left" />
<Button Content="Right Button" controlsToolkit:DockPanel.Dock="Right" />
<Button Content="Fill Button" />
</controlsToolkit:DockPanel>
将会形成如下一个的布局:
有趣的是,DockPanel形成的布局与控件的顺序是有关系的。例如,把LeftButton和Bottom Button顺序换一下,由于左按钮先Dock,会把除了上按钮以外的整个左侧占用,那么,底部Button就会被左按钮挤压而短掉一截。
我们可以在某一个方向上Dock多个控件,它们的行为就像被放在不同Orientation的StackPanel中一样。
Grid
Grid,中文翻译为格,我们可以把它看成是一张看不见的表格。这样的排布方式,早在HTML静态网站盛行时就常被应用在网页的布局里。Grid为Silverlight提供了自由而强大的布局能力。
我们可以通过RowDefinitions和ColumnDefinitions来定义行和列。例如,我们要创建一个3行2列的Grid:
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
为了看见定义的行和列,这里,我们把ShowGridLines设置为True。我们可以利用这个属性帮助我们进行布局,待布局完成了,再将其设置为False。以上XAML得到结果如下:
通过AttachedProperty,我们把控件放到指定的行和列中(默认为0行0列)。例如,以下代码会把一个按钮放到2行2列的Cell里。
...
<Button Content="Where am I?" Grid.Column="1" Grid.Row="1" />
</Grid>
在Grid里,行和列是从0开始数的,所以,2行2例应该设置其Row和Column为1。运行效果如下:
对于行或者列的大小,我们可以通过设置,让其拥有3种不同的策略。
l 固定值
确定的像素数。这种策略可以应用于一些特别的应用,例如,定义一些空的行或列来代替Margin的值。其可扩展性在三种策略中最差。
l 自动大小
行或列自动选择大小,以供显示内容——内容需要多少空间,它就占用多少空间。
l 比例
每一行或列占用一定的比例。
例如,要形成一个高20像素的行,我们可以写:
自动大小,可以使用“Auto”作为值:
按比例:
<RowDefinition Height="2*" />
在星号前面添加权重值来改变所占的比例。当星号前面没有数字时,权重默认为1。因此,在上面的代码中,第一行占1/3,第二行占2/3。
有时,我们需要添加一个跨行或者跨列的控件,这个的需求常常出现。我们可以通过设置Grid.SpanRow或者Grid.SpanColumn来实现。例如:
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="Where am I?" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" />
</Grid>
由于在Button中设置了Grid.RowSpan为2,这个Button会向下跨一行,得到如下效果,整个按钮占据了第二列的第二、三行:
有的时候,我们会希望用户根据页面内容或者自己的喜好,来调整Grid行或列的大小。Silverlight为我们提供了GridSpilitter的控件来完成这样的任务。例如,我们在上例中,添加一个纵向的GridSpiletter,用户可以自行调整左右部分的大小。
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="Where am I?" Grid.Column="2" Grid.Row="1" Grid.RowSpan="2" />
<controls:GridSplitter Grid.Column="1" HorizontalAlignment="Center" Margin="0,10,0,10" Width="10" Background="LightBlue" Name="GridSplitter1" VerticalAlignment="Stretch" Grid.RowSpan="3" />
</Grid>
得到:
在一开始使用GridSpiltter的时候,你可能会觉得不大适应,有时候会得不到想要的效果,不能确定当用户拖动GridSpilitter的时候,不能确信界面上会发生什么。其实,要用好GridSpilitter,需要注意以下几点:
l 把GridSpilitter放在单独的Cell里。虽然你也可以把GridSpilitter放在有内容的Cell里,但是,此时必须设置其它控件的Margin以免内容被GridSpilitter挡住,这等于是给自己添麻烦。不如放在一个单独的Cell里,然后设置这行或列的高度或宽度为Auto。
l GridSpilitter调整整个行或列的大小,而不会在意其自身占了几行或几列。因此,通常设置它的RowSpan或者ColumnSpan让它穿过所有的列或行。
l 要添加一个垂直的GridSpilitter,要设置它的VerticalAlignment为拉申;如果是水平GridSpilitter,则设置HorizontalAlignment为拉申。另一个值可以设置为固定大小。
l 如果行或者列设置了最小值,那么,GridSpilitter不会破坏最小值。
Canvas
事实上,大部分WinForm程序员最初转向Web开发时,总会怀念WinForm的布局方式,给一个起始点,然后设置一个大小,一切皆在掌控之间——多么简洁。呵呵,这个的模式虽然最简单。举个例子,要在两个紧靠在一起的按钮之间添加一个按钮,除了一添加按钮的代码之外,还需要调整原有的按钮的位置。如果按钮被放在StackPanel里,恐怕就不需要为按钮调整费劲了。
如果在设计时需要添加控件还好,要是在运行时要添加控件,那程序员会需要花很多时间在写控件而已的逻辑上。大家都做过一些项目,一定都会遇到类似的问题。
既然如此,为什么Silverlight里还要提供Canvas?因为如果你用它来编写一些基于坐标的程序时,它会带来很大的方便,例如把它作为一个画布来编写绘图工具,或者用它来开发一个需要坐标运算的游戏……
Canvas的使用也非常直接:
<Button Canvas.Left="10" Canvas.Top="15" Content="Coordinate: 10,15" MinHeight="60" />
<Button Canvas.Left="60" Canvas.Top="80" Content="Corrdinate: 60,80" MinHeight="80" />
<Button Canvas.Left="100" Canvas.Top="90" Content="Corrdinate: 60,80" MinHeight="80" />
</Canvas>
这里,定义了三个按钮,每一个按钮都设定了其在Canvas中的Left和Top。其中,两个按钮有一部分重叠,运行结果如下:
写在最后:
一个实际的页面,可能会同时运用到多种布局方式,实际项目中需要根据需求,灵活运用各种布局方式。
例如:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Width="Auto">
<Button x:Name="btnOk" Content="OK" MinWidth="75" Height="24" Margin="5"/>
<Button x:Name="btnCancel" Content="Cancel" MinWidth="75" Height="24" Margin="5" />
</StackPanel>
</Grid>
我们把一个StackPanel嵌在了一个Grid的3行2列的位置,在StackPanel里,又横向的放了两人个Button。
效果如下:
水平有限,如有谬误,还望各位高人多多指点。
Little knowledge is dangerous.