第5部分:布局和事件基础
原文地址:
系列地址:
源代码:
PDF版本:
我将在本课中讨论布局,换句话说如何在应用程序的用户界面上定位或者排列控件。
本课的计划是:
- 我们将讨论两个主要的用于布局和定位的元素:Grid和StackPanel。
- 对于Grid,我将讨论定义行和列以及各种大小调整的选项和技术以充分利用Grid的功能。
- 接着,我们将学习StackPanel,并将应用程序从Grid布局调整为StackPanel布局,这将教会我们Grid和StackPanel之间的一些关键区别。
- 最后,我们将讨论事件处理程序如何在XAML和C#中进行“连接”。
1. 理解网格的基础知识
缺省的Windows Phone页面模板创建了一个称为"ContentPanel"的<Grid>元素。
该Grid元素用于其他控件的布局。它允许您定义行和列,然后每个控件可以请求被放置在哪一行及哪一列中。
缺省的Windows Phone页面模板创建了一个称为"ContentPanel"的<Grid>元素。
在默认的MainPage.xaml页面模板中,在开始和结束标记<Grid></Grid>之间,Grid显示为空,它似乎没有行或列的定义。然而即使他们未被显式定义,默认情况下始终有一个行定义和列定义。他们占据所有可用的垂直和水平空间以代表Grid中一个大的“单元”。任何放置在开始和结束<Grid></Grid>元素之间的项目被认为位于这个隐式的“单元”之中。
2. Grid的行定义 (RowDefinitions) 和列定义 (ColumnDefinitions)以及定义大小
"LayoutRoot" 网格是一个定义了行的Grid。该Grid定义了两行:
请注意含有项目和页面标题的StackPanel将自己放置在第一行中,该StackPanel有一个称为Grid.Row=”0”的属性。您使用从零开始的编号方案引用行和列。
注意通过设置属性Grid.Row=”1”,内容面板网格 (ContentPanel Grid)将自己放置在第二行。
第一行将高度设置为”自动(Auto)”。第二行(行1)设置为"*"。有三种语法用来帮助设置行和列的大小。对于XAML布局,高度和宽度是相对的,并受许多因素的影响。布局引擎考虑所有这些因素以确定在页面上项目的实际位置。
例如,“自动”表示该行的高度足以容纳放置在其中的所有控件。如果最高的控件高度是150像素,那么这就是该行的实际高度。如果它只有100像素,那么那就是该行的高度。因此“自动”表示高度是相对行内部的控件。
星号称为“比例缩放”,它表示该行的高度应该占据所有其余可用的高度。
这里是另一种使用“比例缩放”的简单示例。我创建了一个项目,在内容面板中有针对三行的定义。请注意每一行的高度:
在星号前加上数字的含义是 “对于所有可用的空间,给我余下的1份、2份或3份”。因为所有这些行加起来的总和是6,每个1*相当于可用高度的1/6。因此3*将获得可用高度的一半,如本示例输出所示:
除了自动和比例缩放,您还可以通过像素指定宽度和高度(以及边距)。事实上,当仅给出数字时,它表示像素的值。通常在布局中使用精确的像素并不是个好主意,因为即便是Windows Phone的屏幕也可能有不同的尺寸。相反,对于布局最好使用像自动和比例缩放这样的相对值。
在本示例中(以及原来的PetSounds示例)需要注意的一件事是除非另有指定,控件的宽度和高度都假定为100%。这就是为什么矩形占据整个“单元格”的原因。这就是为什么按钮首先占据整个内容面板网格,以及为什么我要指定按钮为200 x200像素的原因。
我还想指出的是Grid可以包含一组有关列的定义(ColumnDefinitions),您可以在我创建的名称为GridsRowsAndColumns的应用程序中找到它们。这里我们有了一个3 x 3的网格:
结果是一个简单的网格,并且在每个“单元格”中含有一个数字。
对于本示例我希望您注意到的另一件事情是当未指定Grid.Row 或Grid.Column时,它将位于第一行(行0)或第一列(列0)。依靠默认设置可以让您的代码更简洁。
3. Grid单元格的对齐和边距
这里有另一个称为AlignmentAndMargins的示例应用程序。XAML如下所示:
产生这样的结果:
如果您盯着它看一会儿,示例的大部分内容是显而易见的,但是有几个与对齐和边距有关的细微区别。首先,它指出VerticalAlignment和HorizontalAlignment是如何工作的(即使在给定Grid的一个单元格中,对于StackPanel同样适用)。___Alignment属性将控件拉到它们的边界。相反,边界属性将控件推离它们的边界。第二个需要观察的地方是定义边距的奇怪方式:一系列由逗号分隔的数字。这个约定从级联样式表借用而来。这些数字代表从左侧开始顺时针方向的边距像素值。因此,
Margin="10,20,30,40"
表示距左边界10个像素,顶部边界20个像素,右边界30个像素,底部边界40个像素。在这种情况下,边界针对一个单独的Grid单元格。(因为内容面板没有定义任何其他的行定义RowDefinitions和列定义ColumnDefinitions)
刚才我说过使用自动和比例缩放(*)等相对大小来定义高度和宽度通常更好。那么为什么边距要使用像素来定义?边距用精确的像素表示是因为它们通常只是提供两个使用相对值对象之间的间距和填充的小数值,因此边距可以是一个固定值而不影响整体页面的布局。
4. StackPanel 基础
StackPanel是布局的另一种形式。它以从上到下流动的方式排列控件。
在PetSounds项目中,我们定义了以下StackPanel,它用于在MainPage.xaml的顶部创建应用程序标题和页面标题。
两个TextBlock元素占据父元素(LayoutRoot Grid)的整个水平宽度,因此它们被以垂直方式堆叠。让我们将ContentPanel:
从Grid转换为StackPanel:
起初这种转换只将Meow按钮垂直向下移动:
但是如果将Width和HorizontalAlignment属性从Quack按钮删除(我还删除了Button.Background属性):
并且注释掉Meow按钮的Width 和HorizontalAlignment属性,并且在MainPage()构造函数中注释掉Margin:
然后您可以看到两个按钮将以垂直方式在StackPanel中堆叠:
所以正如您所看到的,通过四件事情的联合使用,您可以实现任意想象的布局
- Grid布局,包含RowDefinitions 和ColumnDefinitions
- StackPanel布局
- 使用边距将元素从左侧、顶部、右侧或底部移开
- HorizontalAlignment和VerticalAlignment属性
这就是XAML布局的简单介绍。让我们转到有关事件的介绍。
5. 理解事件
如果您在Channel9观看了C#基础系列(C# Fundamentals series),在最后的视频中我们讨论了事件。在Windows Phone API中,页面和控件在其生命周期的关键时刻或与终端用户交互过程中引发事件。在本系列中我们已经将PlayAudioButton的Click事件“连接”到一个称为“事件处理程序”的方法。当使用术语“事件处理程序”时,它指的是与事件关联的方法。我使用术语“连接”的意思是事件被绑定到特定的事件处理程序方法。一些事件可以被用户触发,比如按钮控件的Click事件,其他事件在事件的生存期内发生,例如Loaded事件在给定的页面或控件对象被手机API运行时引擎实例化后发生。
有两种将页面或控件“连接”到事件处理程序的方法。第一种是使用XAML的属性语法。我们已经在PlayAudioButton示例中做过了:
如果您回想一下,我们输入:
Click="
并且在我们输入结束双引号之前,智能感知询问我们是否需要选择或创建一个事件处理程序。我们告诉它需要以创建一个新的事件处理程序,Visual Studio将使用如下命名约定命名事件处理程序:
NameOfElement_EventName
所以在我们的例子中,事件处理程序的名称是:
PlayAudioButton_Click
Visual Studio同时在XAML页面的代码隐藏文件中创建了一个方法存根,在本例中代码隐藏文件是MainPage.xaml.cs。请记住这就是Visual Studio自动化。当智能感知第一次出现时,我们可以敲键盘上的escape键并手工输入代码。
关联事件与事件处理程序的第二中方法是使用Visual Studio中的属性窗口。
(1)首先您必须确认已经通过将鼠标放置在某个元素上以选择要编辑的控件。元素的名称将出现在顶部的名称字段,这使您知道设置的对象。也就是说您所做的任何设置将针对PlayAudioButton,而不是页面上的另一个控件。
(2)单击闪电图标。这将切换到一个属性列表,在属性窗口中可以更改该控件可以处理的事件。
(3)双击某个特定的文本框以创建控件的事件和事件处理程序之间的关联。双击将自动命名事件并为您在代码隐藏文件中创建一个方法存根。
第三种技术(我们将在本系列中使用数次)是在C#代码中连接事件处理程序。见下面示例中的第35行:
+=运算符在其他上下文中表示“增加并设置”,因此:
x += 1;
等同于
x = x + 1;
在某种意义上,这里也是如此。我们需要“添加并设置”Click事件到另一个事件处理程序方法。是的,myButton的Click事件可以被设置为触发多个事件处理程序的执行。这里我们说:“当Click事件被触发时,将PlayAudioButton_Click事件处理程序添加到应该被执行的事件处理程序的列表。”一个Click事件可以触发一个或更多方法的执行。在我们的应用程序中只有一个事件处理程序 PlayAudioButton_Click将被执行。
您可能想知道为什么那行代码不是这样:
myButton.Click += PlayAudioButton_Click();
请注意_Click后面的括号。回忆一下C# Fundamentals系列,()代表方法调用运算符。换句话说,当我们使用()时,我们告诉运行时立即执行那行代码中的方法。那不是我们想要的,我们仅仅需要关联或指向PlayAudioButton_Click方法。
有关事件处理程序的另一个有趣的注意事项:当我在35行添加了以上代码后,现在我们有两个事件同时指向同一个事件处理程序方法PlayAudioButton_Click。这是完全合法的。那么我们如何确定是哪个按钮触发了方法的执行?
如果您看一下PlayAudioButton_Click方法的定义:
private void PlayAudioButton_Click(object sender, RoutedEventArgs e)
{}
Windows Phone运行时将sender(发送者)作为输入参数传递。因为我们可以将该事件关联到任意控件,并且sender的类型是Object(.Net框架中几乎所有的数据类型最终都从Object类型派生),所以我们需要做的第一件事情是进行一些检查以确定实际的数据类型(你是一个按钮控件吗?你是一个正方形控件吗?),然后将Object强制转换为特定的数据类型。例如,一旦我们将Object强制转换为Button,那么我们就可以访问Button的属性了。
上述代码片段中方法的签名是Windows Phone API中的典型用法。除了输入参数sender,还有RoutedEventArgs参数, 它用于传递事件的额外信息。您将会看到如何在高级场景中使用它们,但是本系列中不会对其进行讨论。
回顾
综上所述,本课的重点是如何控制页面和控件的布局。对于Grid布局,我们学习了使用自动缩放、比例缩放和像素缩放来定义行和列的高度和宽度的不同方法。我们讨论了垂直对齐和水平对齐如何将控件拉到边界,而边距如何将控件推离边界。然后我们讨论了将事件连接到事件处理程序的不同方法以及事件处理程序方法的输入参数。