WPF 布局方式及常用的布局面板
由于 WPF 的窗体中只能包含单个元素,我们需要在窗体中通过容器的嵌套来布局整个窗体的样式。在 WPF 中常用的布局面板有五种,分别是 Grid、StackPanel、WrapPanel、DockPanel 和 Canvas,它们属于 System.Windows.Controls
命名空间,派生自 System.Windows.Controls.Panel
类。
网格 Grid
Grid 以表格形式将元素放在小格子里整齐配列,在使用时需要预先定义所需要的行和列,通常用于框架中页面的整体布局。
- 定义行和列需要分别在
RowDefinitions
和ColumnDefinitions
中添加RowDefinition
和ColumnDefinition
元素,然后通过附加属性Row
和Column
指定元素在哪个格子中,这两个属性都从0
索引,如果未显式指定则默认都为0
,即元素默认位于第一行第一列。 - 通过使用附加属性
RowSpan
和ColumnSpan
可以指定元素占用的单元格数量,可用于从当前单元格起向后(根据索引)跨越多个单元格。 - 行高和列宽需要分别在
RowDefinition
和ColumnDefinition
的Height
和Width
属性中指定。 - 每个单元格可以留空,也可以有多个元素,当有多个元素时,它们按照
Panel.ZIndex
属性来决定显示的顺序。 - 单元格的框线默认不可见,可将
ShowGridLines
设置为True
来显示网格线以便于调试。
行高和列宽
为了获得最大的灵活性,可以混合使用以下三种尺寸设置方式。
类型 | 值 | 说明 |
---|---|---|
自动尺寸 | Auto | 取尽可能小的值,通常为单元格内元素所占用的最小尺寸 |
相对尺寸 | N* | 取尽可能大的值,如果有多个列或行被定义为相对尺寸则按照几者之间所定义的比例分配尺寸;N 为比例,如果写为 * 则相当于 1* ,如果省略 Height 和 Width 属性则默认为 1* |
绝对尺寸 | N | 使用设备无关单位准确地设置尺寸 |
可拖动分割条
为了允许用户通过拖动来改变行和列的尺寸,可以使用 GridSplitter
元素。
- 由于拖动分割条总是会改变整列或整行的尺寸(而非单个单元格),为了使其外观与行为保持一致,需要通过设置
RowSpan
或ColumnSpan
属性让其跨越整列或整行而不是将其限制在单元格中。 - 分割条的默认尺寸很小,需要设置
Width
或Height
属性以使其可用,垂直分割条需要设置宽度,水平分割条则需要设置高度。 - 为了使其行为正常,需要设置
VerticalAlignment
(垂直对齐方式)和HorizontalAlignment
(水平对齐方式),详情见下表。 - 可以通过设置
ShowPreview
属性为True
来让拖动时显示预览,在松开鼠标左键后才会改变尺寸,默认为False
即拖动时立即改变尺寸。
类型 | VerticalAlignment | HorizontalAlignment |
---|---|---|
垂直可两方向调整行尺寸 | Center | Stretch |
垂直仅可向上调整行尺寸 | Top | Stretch |
垂直仅可向下调整行尺寸 | Bottom | Stretch |
水平可两方向调整列尺寸 | Stretch | Center |
水平仅可向左调整列尺寸 | Stretch | Left |
水平仅可向右调整列尺寸 | Stretch | Right |
ActualHeight 大于等于 ActualWidth 则为水平可两方向调整列尺寸,否则为垂直可两方向调整行尺寸 | Stretch | Stretch |
示例
网格排列
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <!-- 默认为 Height="1*" --> <RowDefinition Height="*" /> <!-- 等于 Height="1*" --> <RowDefinition Height="Auto" /> <RowDefinition Height="50" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <!-- 默认为 Width="1*" --> <ColumnDefinition Width="2*" /> </Grid.ColumnDefinitions> <Button>First</Button> <!-- 默认为 Grid.Row="0" Grid.Column="0" --> <Button Grid.Row="1">Second</Button> <!-- 默认为 Grid.Column="0" --> <Button Grid.Row="2">Third</Button> <Button Grid.Row="3">Fourth</Button> <Button Grid.Column="1">Fifth</Button> <!-- 默认为 Grid.Row="0" --> <!-- 留空一个单元格 --> <Button Grid.Row="2" Grid.Column="1" Grid.RowSpan="2">Sixth</Button> <!-- 占用两个单元格 --> </Grid> </Window>
简单的网格布局
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="250" Width="550"> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Margin" Value="5,5,0,0" /> <Setter Property="Padding" Value="10,2" /> </Style> </Grid.Resources> <TextBox Grid.ColumnSpan="3">Hello World!</TextBox> <Button Grid.Row="1" Grid.Column="1" Content="OK" /> <Button Grid.Row="1" Grid.Column="2" Content="Cancel" /> </Grid> </Window>
拖动分割条
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="2*" /> </Grid.ColumnDefinitions> <Grid.Resources> <Style TargetType="Label"> <Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="FontSize" Value="30" /> </Style> </Grid.Resources> <Label Content="A" Grid.Row="0" Grid.Column="0" /> <Label Content="B" Grid.Row="0" Grid.Column="2" /> <Label Content="C" Grid.Row="2" Grid.Column="0" /> <Label Content="D" Grid.Row="2" Grid.Column="2" /> <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Width="5" VerticalAlignment="Stretch" HorizontalAlignment="Center" /> <GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Height="5" VerticalAlignment="Center" HorizontalAlignment="Stretch" /> </Grid> </Window>
拖动分割条,行和列的尺寸被改变了。
栈面板 StackPanel
StackPanel 可以将元素排列成一行或者一列,其特点是每个元素各占一行或者一列。
- 默认情况下,水平排列时,每个元素都与面板一样高;垂直排列时,每个元素都与面板一样宽。如果包含的元素超过了面板空间则多出的内容会被截断。
- 默认情况下,元素为垂直排列,由
Orientation
属性决定是水平排列Horizontal
还是垂直排列Vertical
。 - 当元素空间大于其内容的空间时,剩余空间将由
HorizontalAlignment
和VerticalAlignment
属性来决定如何分配。 - 当水平排列且
FlowDirection
为RightToLeft
时,将从右向左排列元素。
示例
垂直方向排列,每个元素都与面板一样宽
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <StackPanel> <!-- 默认为垂直排列 --> <Button>First</Button> <Button>Second</Button> <Button>Third</Button> </StackPanel> </Window>
水平方向排列,每个元素都与面板一样高
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <StackPanel Orientation="Horizontal"> <Button>First</Button> <Button>Second</Button> <Button>Third</Button> </StackPanel> </Window>
垂直方向排列且垂直居中对齐
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <StackPanel VerticalAlignment="Center"> <Button>First</Button> <Button>Second</Button> <Button>Third</Button> </StackPanel> </Window>
环绕面板 WrapPanel
WrapPanel 可以将元素从左至右按照行或列的顺序罗列,当长度或高度不够时就会自动进行换行,后续排序按照从上至下或从右至左的顺序进行。
- 默认情况下,元素为水平排列,由
Orientation
属性决定是水平排列上下换行(Horizontal
)还是垂直排列左右换行(Vertical
)。 - 由
ItemHeight
和ItemWidth
指定所有子元素一致的高度和宽度,每个子元素填充其高度和宽度的方式取决于它的VerticalAlignment
和HorizontalAlignment
属性,具体的高度和宽度取决与它的Height
属性和Width
属性,任何超出ItemHeight
和ItemWidth
的子元素将会被截断。
示例
水平方向排列,宽度不够时自动换行
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <WrapPanel> <!-- 默认为水平排列 --> <Button>First</Button> <Button>Second</Button> <Button>0123456789012345678901234567890123456789012345678901234567890123456789</Button> <Button>Third</Button> </WrapPanel> </Window>
垂直方向排列,高度不够时自动换行
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <WrapPanel Orientation="Vertical"> <Button>First</Button> <Button Height="270">Second</Button> <Button>0123456789012345678901234567890123456789012345678901234567890123456789</Button> <Button>Third</Button> </WrapPanel> </Window>
停靠面板 DockPanel
DockPanel 可以定义一个区域,在此区域中子元素将通过描点的形式排列,这些对象位于 Children
属性中。
- 子元素将会被排序并根据附加属性
Dock
指定的边进行停靠,未显式指定停靠方向的元素默认停靠在Left
,多个停靠在同侧的元素则按顺序排序。 - 默认情况下,后添加的元素只能使用剩余空间,无论对最后一个子元素设置任何停靠值,该子元素都将始终填满剩余的空间,如果不希望最后一个元素填充剩余区域,可以将属性
LastChildFill
设置为False
。
示例
最后的元素填充剩余空间
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <DockPanel> <Button DockPanel.Dock="Top">First</Button> <Button DockPanel.Dock="Top">Second</Button> <Button DockPanel.Dock="Left">0123456789</Button> <Button DockPanel.Dock="Right">Third</Button> <Button DockPanel.Dock="Bottom">Fourth</Button> <Button>Fifth</Button> </DockPanel> </Window>
最后的元素不填充剩余空间且按顺序停靠
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <DockPanel LastChildFill="False"> <Button DockPanel.Dock="Top">First</Button> <Button DockPanel.Dock="Top">Second</Button> <Button DockPanel.Dock="Left">0123456789</Button> <Button DockPanel.Dock="Right">Third</Button> <Button DockPanel.Dock="Bottom">Fourth</Button> <Button DockPanel.Dock="Top">Fifth</Button> </DockPanel> </Window>
画布 Canvas
Canvas 是一个基于坐标的布局面板,用于完全控制每个元素的精确位置,主要来布置图面。
- 使用
Left
、Right
、Top
和Bottom
四个附加属性指定元素相对于画布的位置,未指定位置的元素将出现在画布的左上角。当画布的大小被改变时,其子元素的位置也会相对于四个边的距离进行移动。 - 使用
Panel.ZIndex
指定子元素的层级关系。 - 子元素的部分或全部都可超过画布的边界,默认不会被裁剪,同时可以使用负坐标,即溢出的内容会显示在画布之外,这是因为默认
ClipToBounds=False
,因此画布不需要指定大小。
示例
按照控件顺序堆叠显示,可指定层级关系
<Window x:Class="WPFDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="360" Width="550"> <Canvas> <Canvas.Resources> <Style TargetType="Button"> <Setter Property="Height" Value="30" /> <Setter Property="Width" Value="70" /> </Style> </Canvas.Resources> <Button Canvas.Left="250" Canvas.Top="100">First</Button> <Button Canvas.Left="260" Canvas.Top="120">Second</Button> <Button Canvas.Left="270" Canvas.Top="140">Third</Button> <Button Canvas.Left="280" Canvas.Top="160">Fourth</Button> <Button Canvas.Left="210" Canvas.Top="100" Panel.ZIndex="-1" Height="50">Bottom</Button> </Canvas> </Window>
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 雪狐的小窝!