布局面板

布局面板是容器,可用于排列和分组应用中的 UI 元素。 内置 XAML 布局面板包括 RelativePanel、 StackPanel、 Grid、 VariableSizedWrapGrid 和 Canvas。 在这里,我们将介绍每个面板,并演示如何使用它来布局 XAML UI 元素。

选择布局面板时需要考虑以下几个事项:

  • 面板如何定位其子元素。
  • 面板如何调整其子元素大小。
  • 重叠的子元素如何相互叠放 (z-order)。
  • 创建所需布局所需的嵌套面板元素的数量和复杂性。

例子

WinUI 3 示例集图标 WinUI 3 示例库应用包含 WinUI 控件和功能的交互式示例。 从 Microsoft Store 或浏览 GitHub 上的源代码获取应用。

面板属性

在讨论各个面板之前,让我们了解所有面板具有的一些常见属性。

面板附加属性

大多数 XAML 布局面板使用附加属性让其子元素告知父面板如何在 UI 中定位它们。 附加属性使用语法 AttachedPropertyProvider.PropertyName。 如果你有嵌套在其他面板内的面板,则指定父级布局特征的 UI 元素上的附加属性仅由最直接的父面板解释。

下面是有关如何在 XAML 中的 Button 控件上设置 Canvas.Left 附加属性的示例。 这会通知父画布,该按钮应定位在距画布左边缘 50 个有效像素的位置。

<Canvas>
  <Button Canvas.Left="50">Hello</Button>
</Canvas>

有关附加属性的详细信息,请参阅附加属性概述。

面板边框

RelativePanel、StackPanel 和网格面板定义边框属性,使你可以在面板周围绘制边框,而无需将它们包装到其他 Border 元素中。 边框属性为 BorderBrush、 BorderThickness、 CornerRadius 和 Padding。

下面是如何在网格上设置边框属性的示例。

<Grid BorderBrush="Blue" BorderThickness="12" CornerRadius="12" Padding="12">
    <TextBlock Text="Hello World!"/>
</Grid>

带边框的网格

使用内置边框属性可减少 XAML 元素计数,从而提高应用的 UI 性能。 有关布局面板和 UI 性能的详细信息,请参阅 “优化 XAML 布局”。

RelativePanel

RelativePanel 通过指定 UI 元素相对于其他元素和面板的位置来布局 UI 元素。 默认情况下,元素位于面板的左上角。 可以将 RelativePanel 与 VisualStateManager 和 AdaptiveTrigger 配合使用,以便针对不同的窗口大小重新排列 UI。

下表显示了可用于将元素相对于面板或其他元素对齐的附加属性。

面板对齐 同级对齐 同级位置
AlignTopWithPanel AlignTopWith 以上
AlignBottomWithPanel AlignBottomWith 下面
AlignLeftWithPanel AlignLeftWith LeftOf
AlignRightWithPanel AlignRightWith RightOf
AlignHorizontalCenterWithPanel AlignHorizontalCenterWith  
AlignVerticalCenterWithPanel AlignVerticalCenterWith  

此 XAML 演示如何排列 RelativePanel 中的元素。

<RelativePanel BorderBrush="Gray" BorderThickness="1">
    <Rectangle x:Name="RedRect" Fill="Red" Height="44" Width="44"/>
    <Rectangle x:Name="BlueRect" Fill="Blue"
               Height="44" Width="88"
               RelativePanel.RightOf="RedRect" />

    <Rectangle x:Name="GreenRect" Fill="Green" 
               Height="44"
               RelativePanel.Below="RedRect" 
               RelativePanel.AlignLeftWith="RedRect" 
               RelativePanel.AlignRightWith="BlueRect"/>
    <Rectangle Fill="Orange"
               RelativePanel.Below="GreenRect" 
               RelativePanel.AlignLeftWith="BlueRect" 
               RelativePanel.AlignRightWithPanel="True"
               RelativePanel.AlignBottomWithPanel="True"/>
</RelativePanel>

结果如下所示。

相对面板

以下是有关矩形大小调整的一些注意事项:

  • 红色矩形的显式大小为 44x44。 它放置在面板的左上角,这是默认位置。
  • 绿色矩形的显式高度为 44。 其左侧与红色矩形对齐,右侧与蓝色矩形对齐,确定其宽度。
  • 橙色矩形未给出显式大小。 其左侧与蓝色矩形对齐。 它的右边缘和下边缘与面板的边缘对齐。 其大小由这些对齐方式决定,并且会随着面板调整大小而调整大小。

StackPanel

StackPanel 将其子元素排列成可水平或垂直方向的单个线条。 StackPanel 通常用于在页面上排列 UI 的小子节。

可以使用 Orientation 属性指定子元素的方向。 默认方向为 Vertical。

以下 XAML 演示如何创建项的垂直 StackPanel。

<StackPanel>
    <Rectangle Fill="Red" Height="44"/>
    <Rectangle Fill="Blue" Height="44"/>
    <Rectangle Fill="Green" Height="44"/>
    <Rectangle Fill="Orange" Height="44"/>
</StackPanel>

结果如下所示。

堆栈面板

在 StackPanel 中,如果未显式设置子元素的大小,它将拉伸以填充可用宽度(或高度(如果方向为 水平)。 在此示例中,未设置矩形的宽度。 矩形会扩展以填充 StackPanel 的整个宽度。

网格

网格面板支持动态布局,并允许你在多行和多列布局中排列控件。 使用 RowDefinitions 和 ColumnDefinitions 属性指定网格的行和列。

若要在 Grid 的特定单元格中定位对象,请使用 Grid.Column 和 Grid.Row 附加属性。

若要使内容跨越多个行和列,请使用 Grid.RowSpan 和 Grid.ColumnSpan 附加属性。

此 XAML 示例演示如何创建包含两行和两列的网格。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="44"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Rectangle Fill="Red" Width="44"/>
    <Rectangle Fill="Blue" Grid.Row="1"/>
    <Rectangle Fill="Green" Grid.Column="1"/>
    <Rectangle Fill="Orange" Grid.Row="1" Grid.Column="1"/>
</Grid>

结果如下所示。

网格

在此示例中,尺寸调整的运作原理如下所示:

  • 第二行的显式高度为 44 个有效像素。 默认情况下,第一行的高度填充剩余的空间。
  • 第一列的宽度设置为“自动”,因此其宽度与其子项所需的宽度一致。 在这种情况下,其宽度为 44 个有效像素,以适应红色矩形的宽度。
  • 矩形没有其他尺寸限制,因此每个矩形都会拉伸以填满所在的网格单元格。

你可以使用“自动”或星号大小调整在列或行内分布空间。 使用自动调整大小让 UI 元素调整大小以适应其内容或父容器。 还可以对网格的行和列使用自动大小调整功能。 若要使用自动大小调整,请将 UI 元素的高度和/或宽度设置为 “自动”。

使用比例大小调整(也称为 星型调整)按加权比例在网格的行和列之间分配可用空间。 在 XAML 中,星值表示为 * (或 n* 表示加权星形大小)。 例如,若要指定一列比 2 列布局中的第二列宽 5 倍,请使用“5*”和“*”作为 ColumnDefinition 元素中的 Width 属性。

此示例在具有 4 列的网格中组合了固定、自动和按比例大小调整。

列 调整大小 Description
第一列 自动 该列的大小将适合其内容。
Column_2 * 在计算“自动”列之后,该列将获得剩余宽度的一部分。 Column_2宽度为Column_4的一半。
Column_3 44 这一列的宽度为 44 像素。
第四列 2* 在计算“自动”列之后,该列将获得剩余宽度的一部分。 Column_4宽度为Column_2两倍。

默认列宽为“*”,因此无需为第二列显式设置此值。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition/>
        <ColumnDefinition Width="44"/>
        <ColumnDefinition Width="2*"/>
    </Grid.ColumnDefinitions>
    <TextBlock Text="Column 1 sizes to its content." FontSize="24"/>
</Grid>

在Visual Studio XAML 设计器中,结果如下所示。

Visual Studio设计器中的 四列网格

VariableSizedWrapGrid

VariableSizedWrapGrid 是网格样式的布局面板,当达到 MaximumRowsOrColumns 值时,行或列会自动换行到新行或列。

方向属性指定网格在换行之前是以行还是以列的形式添加其项。 默认方向为 Vertical,这意味着网格将项从上到下添加,直到列已满,然后换行到新列。 当值为 Horizontal 时,网格将项从左到右添加,然后换行到新行。

单元格维度由 ItemHeight 和 ItemWidth 指定。 每个单元格的大小相同。 如果未指定 ItemHeight 或 ItemWidth,则第一个单元格大小以适应其内容,而其他每个单元格都是第一个单元格的大小。

可以使用 VariableSizedWrapGrid.ColumnSpan 和 VariableSizedWrapGrid.RowSpan 附加属性来指定子元素应填充的相邻单元格数。

下面介绍如何在 XAML 中使用 VariableSizedWrapGrid。

<VariableSizedWrapGrid MaximumRowsOrColumns="3" ItemHeight="44" ItemWidth="44">
    <Rectangle Fill="Red"/>
    <Rectangle Fill="Blue" 
               VariableSizedWrapGrid.RowSpan="2"/>
    <Rectangle Fill="Green" 
               VariableSizedWrapGrid.ColumnSpan="2"/>
    <Rectangle Fill="Orange" 
               VariableSizedWrapGrid.RowSpan="2" 
               VariableSizedWrapGrid.ColumnSpan="2"/>
</VariableSizedWrapGrid>

结果如下所示。

可变大小换行网格

在此示例中,每列中的最大行数为 3。 第一列仅包含 2 个项目(红色和蓝色矩形),因为蓝色矩形跨越 2 行。 然后,绿色矩形将折行到下一列的顶部。

画布

画布面板使用固定坐标点定位其子元素,不支持流畅布局。 可以通过在每个元素上设置 Canvas.Left 和 Canvas.Top 附加属性来指定各个子元素上的点。 父级 Canvas 在布局的 Arrange 传递期间从其子级中读取这些附加属性值。

Canvas 中的对象可以重叠,其中一个对象在另一个对象之上绘制。 默认情况下,Canvas 按声明子对象的顺序呈现子对象,因此最后一个子对象呈现在顶部(每个元素的默认 z 索引为 0)。 这与其他内置面板相同。 但是,Canvas 还支持可以在每个子元素上设置的 Canvas.ZIndex 附加属性。 可以在代码中设置此属性,以在运行时更改元素的绘制顺序。 具有最高 Canvas.ZIndex 值的元素最后绘制,因此会覆盖共享相同空间或以任何方式重叠的任何其他元素。 请注意,alpha 值(透明度)会被遵循,因此,即使元素重叠,如果上层元素具有非最大 alpha 值,重叠区域中显示的内容可能会被混合。

画布不对其子元素进行任何大小调整。 每个元素必须指定其大小。

下面是 XAML 中的 Canvas 示例。

<Canvas Width="120" Height="120">
    <Rectangle Fill="Red" Height="44" Width="44"/>
    <Rectangle Fill="Blue" Height="44" Width="44" Canvas.Left="20" Canvas.Top="20"/>
    <Rectangle Fill="Green" Height="44" Width="44" Canvas.Left="40" Canvas.Top="40"/>
    <Rectangle Fill="Orange" Height="44" Width="44" Canvas.Left="60" Canvas.Top="60"/>
</Canvas>

结果如下所示。

Canvas

使用画布面板时要谨慎。 尽管在某些方案中能够精确控制 UI 中元素的位置是很方便的,但固定定位布局面板会导致 UI 的该区域不太适应整个应用窗口大小的变化。 应用窗口大小调整可能由设备方向更改、拆分窗口、更换显示器和许多其他使用场景引起。

WrapPanel

Important

WrapPanel从 Windows 应用 SDK 2.0 实验 2 开始提供。 Windows 应用 SDK 2.0 实验 4 将其间距属性重命名为ItemSpacing和 LineSpacing。 API 标记为实验性,可能会在将来的版本中更改或删除。 请勿在生产应用中使用它们。

WrapPanel 在一行中逐个排列其子元素。 当下一个元素不适合行上保留的空间时,面板会将其移动到新行的开头。 对一组元素(如标记或筛选器按钮)使用 WrapPanel,这些元素应在可用空间发生更改时重新排列。 WrapPanel 是 Windows Community Toolkit WrapPanel 的移植版本。

Orientation 属性指定面板添加项的方向。 默认方向为 水平,这意味着面板将项从左到右添加,然后换行到新行。 当值为 Vertical 时,面板会将项从上到下添加,然后换行到新列。 当面板的 FlowDirection 为 RightToLeft 时,例如,在为从右向左语言本地化的应用中,面板将镜像此布局。 它从右到左填充每一行,并将每个新列添加到前一列的左侧。

与 VariableSizedWrapGrid 不同,WrapPanel 不使用固定大小的单元格。 每个子级沿布局方向保持其所需大小。 在一行中,每个子级都会获得一个高度与该行中最高的子级一样高的槽。 在列中,每个子级都会分配到一个槽,其宽度与该列中最宽的子级一样宽。

使用这些属性向布局添加空间:

  • ItemSpacing 设置同一行上相邻项之间的间距。 默认值为 0。
  • LineSpacing 设置行之间的间距。 默认值为 0。
  • 填充 设置面板边缘与其项之间的间距。

下面介绍如何在 XAML 中使用 WrapPanel。

<WrapPanel Width="132" ItemSpacing="4" LineSpacing="4">
    <Rectangle Fill="Red" Width="44" Height="44"/>
    <Rectangle Fill="Blue" Width="80" Height="44"/>
    <Rectangle Fill="Green" Width="44" Height="44"/>
    <Rectangle Fill="Orange" Width="60" Height="44"/>
</WrapPanel>

结果如下所示。

一个 WrapPanel,第一行上有红色和蓝色矩形,第二行上有绿色和橙色矩形

在此示例中,红色和蓝色矩形及其之间的项间距使用面板的 132 像素中的 128 个,因此它们共享第一行。 绿色矩形无法放入剩余空间,因此它换行到第二行。 橙色矩形正好放在第二行的绿色矩形旁边。

若要使最后一个子级填充其行上保留的空间,请将 ItemsStretch 属性设置为 Last。 默认值 None 使每个子项保持其所需大小。 不要在最后一个子级上设置显式宽度,也不要在方向为 垂直时设置显式高度,因为显式大小会阻止子级拉伸。

<WrapPanel Width="132" ItemSpacing="4" LineSpacing="4" ItemsStretch="Last">
    <Rectangle Fill="Red" Width="44" Height="44"/>
    <Rectangle Fill="Blue" Width="80" Height="44"/>
    <Rectangle Fill="Green" Width="44" Height="44"/>
    <Rectangle Fill="Orange" Height="44"/>
</WrapPanel>

结果如下所示。

一个 WrapPanel,其中橙色矩形拉伸以填充第二行的其余部分

在此示例中,橙色矩形填充第二行的其余部分。 若要在代码中设置值,请使用 WrapPanelItemsStretch 枚举。

// Stretch the last child of a WrapPanel named tagPanel.
tagPanel.ItemsStretch = WrapPanelItemsStretch.Last;

WrapPanel 仅在其排列方向上可用空间受限时,才包装其子级。 例如,水平 StackPanel 不会限制其子级的宽度,因此其内部的水平 WrapPanel 在一行中显示其所有子级。 面板不保留空间,也不会为可见性为折叠的子级添加间距。

WrapPanel 不会虚拟化其子级。 它创建并布局每个子级,包括滚动出视图的子级。 若要显示可自动换行的大型集合,请使用 GridView 或带有 UniformGridLayout 的 ItemsRepeater。 这些控件支持 UI 虚拟化。

ItemsControl 的面板

有几个特殊用途面板只能用作 ItemsPanel 来显示 ItemsControl 中的项。 这些是 ItemsStackPanel、 ItemsWrapGrid、 VirtualizingStackPanel 和 WrapGrid。 不能将这些面板用于常规 UI 布局。

获取示例代码