理解XAML应用程序中的布局管理是开发成功Silverlight应用的一个重要方面。对于大多数来自Web领域的人来说,如果你对CSS不熟悉,那么这将成为你面临的最大的挑战之一。
理解布局选项
Silverlight提供了一个灵活的系统用于在页面上布置界面元素。布局模型同时支持绝对定位和相对定位的布局风格。虽然提供了多种布局控件,但最常用的是:
- Canvas
- StackPanel
- Grid
让我们逐个看看当把元素放在其中的时候,它们是如何工作的。我们将使用一个简单的按钮元素来演示。我们使用本系列第一部分创建的项目,并在Home.xaml页面作简单的演示。
Canvas
Canvas是最基础的布局控件,被用于通过坐标绝对地定位元素。你可以通过在Canvas中附加属性来定位元素。附加属性允许父容器(在这个例子中是Canvas)扩展在它之中的控件的属性(在这个例子中是按钮)。我们可以在Canvas中像这样放置很多按钮:
1: <Canvas>
2: <Button Canvas.Top="50" Canvas.Left="50" Content="Button 1" FontSize="18" Width="150" Height="45" />
3: <Button Canvas.Top="150" Canvas.Left="20" Content="Button 2" FontSize="18" Width="150" Height="45" />
4: <Button Canvas.Top="70" Canvas.Left="80" Canvas.ZIndex="99" Content="Button 3" FontSize="18" Width="150" Height="45" />
5: </Canvas>
显示效果如下:
可以看到,这是绝对定位布局的方法。注意到在这个例子中,我也可以通过指定扩展属性ZIndex使得一个按钮叠加在另一个上面。这可能有助于像是游戏开发或者高物理环境这样对计算非常精确的场合。Canvas在位置移动不太大,或是你想漂亮地控制应用程序的尺寸时是很有用的。然而,Canvas有时不像StackPanel和Grid那么容易利用。
StackPanel
StackPanel是垂直或水平堆叠元素的布局控件(默认是垂直的)。示例代码如下:
1: <StackPanel>
2: <Button Margin="10" Content="Button 1" FontSize="18" Width="150" Height="45" />
3: <Button Margin="10" Content="Button 2" FontSize="18" Width="150" Height="45" />
4: <Button Margin="10" Content="Button 3" FontSize="18" Width="150" Height="45" />
5: </StackPanel>
显示效果如下:
或者如果我们通过代码将默认的方向属性改成水平的(注意方向属性是唯一的区别):
1: <StackPanel Orientation="Horizontal">
2: <Button Margin="10" Content="Button 1" FontSize="18" Width="150" Height="45" />
3: <Button Margin="10" Content="Button 2" FontSize="18" Width="150" Height="45" />
4: <Button Margin="10" Content="Button 3" FontSize="18" Width="150" Height="45" />
5: </StackPanel>
显示效果如下:
StackPanel提供了一种使元素互相顶着或靠着的简单方式,让你不必花很大精力在定位容器中的控件元素。
Grid
Grid是大多数情况下最灵活的布局(注意我说的是大多数,不是所有)。和听起来一样,Grid通过行与列来安排位置。不像网站开发可能要在<table>元素中放置<tr>、<td>标签那样,XAML Grid用法是不同的。你定义Grid的整体结构以后,通过附加属性告诉元素把它们放在哪里。
考虑如下代码(注意我明确地显示了Grid的网格,但你不必每次都那么做,我只是为了使显示更加形象化)。
1: <Grid ShowGridLines="True">
2: <Grid.RowDefinitions>
3: <RowDefinition Height="60" />
4: <RowDefinition Height="60" />
5: <RowDefinition Height="60" />
6: </Grid.RowDefinitions>
7:
8: <Grid.ColumnDefinitions>
9: <ColumnDefinition Width="175" />
10: <ColumnDefinition Width="175" />
11: <ColumnDefinition Width="175" />
12: </Grid.ColumnDefinitions>
13:
14: <Button Grid.Column="0" Grid.Row="0" Content="Button 1" FontSize="18" Width="150" Height="45" />
15: <Button Grid.Column="2" Grid.Row="0" Margin="10" Content="Button 2" FontSize="18" Width="150" Height="45" />
16: <Button Grid.Column="1" Grid.Row="2" Margin="10" Content="Button 3" FontSize="18" Width="150" Height="45" />
17: </Grid>
我们定义了一个3行3列的Grid并定义了宽和高。注意在按钮元素中我们使用了附加属性来定位它们在Grid中的位置。
显示效果如下:
注意按钮上的附加属性(Grid.Column、Grid.Row)是如何告诉元素把自己定位在容器的哪个位置的。在Expression Blend中做布局的工作是非常有帮助的。注意在Blend中你可以通过向导可视地指定行和列的定义,同时它将为你生成XAML:
箭头所指的向导告诉你行和列是否是固定尺寸的(锁定)。我们将在我们的应用程序中使用多种布局控件。事实上,我们选择的默认模版已经为我们的应用程序的基本框架提供了所有不同类型的布局控件。
构建我们的Twitter应用程序
现在我们可以构建我们的应用程序了。总的来说,这是我们要做的程序的样例。
注意到我们将为用户提供一个地方去输入搜索条件,并将结果通过列表显示出来。我们也有导航区可以显示不同的内容,像是历史查询或是一些可能的统计。
幸运的是我们选择的导航模版已经送了我们的整体布局一个大礼。我们只需要在MainPage.xaml做一点点改动。在MainPage.xaml的第29行,我们移除应用程序的Logo,然后把它下面的内容改成Twitter Search Monitor。我们将很快回来,不过现在先让我们切换到我们的视图。在视图文件夹的项目结构中右击并在Visual Studio中选择创建新页,并选择创建一个新Silverlight页,将其命名为Search.xaml:
现在你应该已经获得了一个包含默认Grid布局的空白XAML页面。我们将在这里创建在上面看到的搜索屏幕。我们从MainPage.xaml获取头信息是因为我们将使用框架元素来承载我们的应用程序。
Silverlight导航框架
在这一节,让我们切入并了解Silverlight导航框架。如果你还记得我们开始时使用的导航应用程序模版。默认的模版向我们提供了一个MainPage.xaml页面并给出了一个大致的主页视图。导航框架基本上由3部分组成:UriMapper、Frame以及Page。
UriMapper
我喜欢把UriMapper元素当作是各种各样的路由引擎。它并不总是必须的,但我认为它能模糊并简化你的导航终端。你可以用/Home终端来映射/View/Home.xaml终端,使得它更容易阅读并且不会丢失任何技术配置。你以后还可以修改它,让它映射到其它地方。你可以看见UriMapper作为MainPage.xaml页面中框架的一个元素:
1: <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
2: <navigation:Frame.UriMapper>
3: <uriMapper:UriMapper>
4: <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
5: <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
6: </uriMapper:UriMapper>
7: </navigation:Frame.UriMapper>
8: </navigation:Frame>
现在像上面显示的那样,UriMapper在XAML里面。但它也可以包含资源,如果要那样做的话,你可以像这样将它添加到框架元素中(假设资源是UriMapperRoutes):
(此处原文未给出代码)
Frame
如果你是一名ASP.NET开发者,那么你可以把Frame元素当作是ContentPlaceholder控件。Frame是一片可以被导航的区域。你可以指定一个默认的视图,但是之后我们可以看到任何导航都可以在那片区域被触发。看上面的代码,你可以看到一个默认的视图,Frame的Source属性是为应用程序提供路由的"/Home"。
Page
最后要说的基本导航区是只在最后一步创建的Page元素。它们是在Frame元素中显示的非常基本的内容区域。它们与你通常添加的UserControl很相似,但是特别之处在于它们可以导航。我们将考虑把我们的应用程序作为页面元素。
你可以通过以下视频了解更多关于导航的知识:
简单地挖掘一遍它的功能就能受益无穷。框架允许深度连接到Silverlight应用程序。
为我们的搜索视图创建界面布局
让我们为刚刚创建的Search.xaml页面创建一个界面。在这一节你可能对XAML中所有的{StaticResource XXXXXXXX}元素感到困惑,我们将在第五部分的造型/模版章节作详细介绍,所以现在先跳过它们。注意我们的程序样例,我们需要一个输入框、一个按钮和一个数据显示表格。让我们通过Blend来放置它们。要做到这点,需要在Visual Studio中右击Search.xaml并选择在Blend中编辑:
因为Blend与Visual Studio共享同一个项目结构,所以你可以在编码以前同时打开相同的文件来做XAML的可视化编辑。
在Blend中我们将放置一个两行的表格,一行用于放置搜索输入框和按钮,另一行放置结果视图。在首行我们拖入一个StackPanel控件,并在其中放入一个TextBox和一个Button,并将它的方向改为水平。
下一步我们将放置一个DataGrid用于显示数据。因为DataGrid不是核心控件,它在SDK库里,所以我们需要添加对它的引用。你可以在许多地方这样做,但Blend其实会自动为你完成这样的工作。在Blend的工具箱中点击两次箭头并查找DataGrid:
找到以后,选中它并把它拖到第二行。Blend会自动为你添加System.Windows.Controls.Data.dll的引用并改变XAML文件中的标记。
1: <navigation:Page
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: xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
8: xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="TwitterSearchMonitor.Views.Search"
9: d:DesignWidth="640" d:DesignHeight="480"
10: Title="Twitter Search Page">
11: <Grid x:Name="LayoutRoot">
12: <Grid.RowDefinitions>
13: <RowDefinition Height="32"/>
14: <RowDefinition/>
15: </Grid.RowDefinitions>
16: <StackPanel HorizontalAlignment="Left" Margin="0,-32,0,0" VerticalAlignment="Top" Grid.Row="1" Orientation="Horizontal">
17: <TextBox x:Name="SearchTerm" FontSize="14.667" Margin="0,0,10,0" Width="275" TextWrapping="Wrap"/>
18: <Button x:Name="SearchButton" Width="75" Content="SEARCH"/>
19: </StackPanel>
20: <data:DataGrid x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1"/>
21: </Grid>
22: </navigation:Page>
注意顶部的xmlns:data,这就是你为在XAML中使用非核心控件而添加的程序集的引用。接着使用它们你就能在Grid中看到data:Grid元素。你的XAML现在看上去应该和我一样,就像这样:
注意在XAML中,我为TextBox、Button和DataGrid分别设置了x:Name,分别是SearchTerm、SearchButton以及SearchResults,这将帮助我以后很容易地找到它们。
现在如果你回到Visual Studio你将会看到一个询问是否重新读取项目的提示框。这是因为Blend添加了对DataGrid控件的引用,从而改变了项目文件。你可以选择重新读取继续下去。这显示了开发工具是如何在项目文件层面进行整合的,现在我们可以再次用VS来编写代码了。
改变Search.xaml的默认UriMapper
现在我们只是创建了搜索页(这基本上是我们应用程序的主页),接着让我们来为导航框架做一些改变。在MainPage.xaml找到Frame,并将默认的/Home改为我们的搜索页,并且其它几处地方也做同样的更改。你的Frame XAML看起来应该像这样:
1: <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Search" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
2: <navigation:Frame.UriMapper>
3: <uriMapper:UriMapper>
4: <uriMapper:UriMapping Uri="" MappedUri="/Views/Search.xaml"/>
5: <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
6: </uriMapper:UriMapper>
7: </navigation:Frame.UriMapper>
8: </navigation:Frame>
因为我们不需要Home.xaml,所以继续在项目中的其它地方删除它。同时添加一个叫作History.xaml的新视图,同时改变MainPage.xaml的LinkBorder,使它包含一个链接,像这样:
1: <Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}">
2: <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}">
3: <HyperlinkButton x:Name="Link1" Style="{StaticResource LinkStyle}" NavigateUri="/Search" TargetName="ContentFrame" Content="home"/>
4: <Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/>
5: <HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}" NavigateUri="/History" TargetName="ContentFrame" Content="history"/>
6: <Rectangle x:Name="Divider2" Style="{StaticResource DividerStyle}"/>
7: <HyperlinkButton x:Name="Link3" Style="{StaticResource LinkStyle}" NavigateUri="/About" TargetName="ContentFrame" Content="about"/>
8: </StackPanel>
9: </Border>
现在如果我们运行起来,效果应该像这样:
现在我们已经有了一个基础布局