由于 WPF 的窗体中只能包含单个元素,我们需要在窗体中通过容器的嵌套来布局整个窗体的样式。在 WPF 中常用的布局面板有五种,分别是 Grid、StackPanel、WrapPanel、DockPanel 和 Canvas,它们属于 System.Windows.Controls 命名空间,派生自 System.Windows.Controls.Panel 类。

网格 Grid

Grid 以表格形式将元素放在小格子里整齐配列,在使用时需要预先定义所需要的行和列,通常用于框架中页面的整体布局。

  • 定义行和列需要分别在 RowDefinitionsColumnDefinitions 中添加 RowDefinitionColumnDefinition 元素,然后通过附加属性 RowColumn 指定元素在哪个格子中,这两个属性都从 0 索引,如果未显式指定则默认都为 0,即元素默认位于第一行第一列。
  • 通过使用附加属性 RowSpanColumnSpan 可以指定元素占用的单元格数量,可用于从当前单元格起向后(根据索引)跨越多个单元格。
  • 行高和列宽需要分别在 RowDefinitionColumnDefinitionHeightWidth 属性中指定。
  • 每个单元格可以留空,也可以有多个元素,当有多个元素时,它们按照 Panel.ZIndex 属性来决定显示的顺序。
  • 单元格的框线默认不可见,可将 ShowGridLines 设置为 True 来显示网格线以便于调试。

行高和列宽

为了获得最大的灵活性,可以混合使用以下三种尺寸设置方式。

类型说明
自动尺寸Auto取尽可能小的值,通常为单元格内元素所占用的最小尺寸
相对尺寸N*取尽可能大的值,如果有多个列或行被定义为相对尺寸则按照几者之间所定义的比例分配尺寸;N 为比例,如果写为 * 则相当于 1*,如果省略 HeightWidth 属性则默认为 1*
绝对尺寸N使用设备无关单位准确地设置尺寸

可拖动分割条

为了允许用户通过拖动来改变行和列的尺寸,可以使用 GridSplitter 元素。

  • 由于拖动分割条总是会改变整列或整行的尺寸(而非单个单元格),为了使其外观与行为保持一致,需要通过设置 RowSpanColumnSpan 属性让其跨越整列或整行而不是将其限制在单元格中。
  • 分割条的默认尺寸很小,需要设置 WidthHeight 属性以使其可用,垂直分割条需要设置宽度,水平分割条则需要设置高度。
  • 为了使其行为正常,需要设置 VerticalAlignment(垂直对齐方式)和 HorizontalAlignment(水平对齐方式),详情见下表。
  • 可以通过设置 ShowPreview 属性为 True 来让拖动时显示预览,在松开鼠标左键后才会改变尺寸,默认为 False 即拖动时立即改变尺寸。
类型VerticalAlignmentHorizontalAlignment
垂直可两方向调整行尺寸CenterStretch
垂直仅可向上调整行尺寸TopStretch
垂直仅可向下调整行尺寸BottomStretch
水平可两方向调整列尺寸StretchCenter
水平仅可向左调整列尺寸StretchLeft
水平仅可向右调整列尺寸StretchRight
ActualHeight 大于等于 ActualWidth 则为水平可两方向调整列尺寸,否则为垂直可两方向调整行尺寸StretchStretch

示例

  1. 网格排列

    <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>

    网格排列

  2. 简单的网格布局

    <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>

    简单的网格布局

  3. 拖动分割条

    <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
  • 当元素空间大于其内容的空间时,剩余空间将由 HorizontalAlignmentVerticalAlignment 属性来决定如何分配。
  • 当水平排列且 FlowDirectionRightToLeft 时,将从右向左排列元素。

示例

  1. 垂直方向排列,每个元素都与面板一样宽

    <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>

    垂直方向排列

  2. 水平方向排列,每个元素都与面板一样高

    <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>

    水平方向排列

  3. 垂直方向排列且垂直居中对齐

    <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)。
  • ItemHeightItemWidth 指定所有子元素一致的高度和宽度,每个子元素填充其高度和宽度的方式取决于它的 VerticalAlignmentHorizontalAlignment 属性,具体的高度和宽度取决与它的 Height 属性和 Width 属性,任何超出 ItemHeightItemWidth 的子元素将会被截断。

示例

  1. 水平方向排列,宽度不够时自动换行

    <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>

    水平方向排列

  2. 垂直方向排列,高度不够时自动换行

    <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

示例

  1. 最后的元素填充剩余空间

    <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>

    最后的元素填充剩余空间

  2. 最后的元素不填充剩余空间且按顺序停靠

    <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 是一个基于坐标的布局面板,用于完全控制每个元素的精确位置,主要来布置图面。

  • 使用 LeftRightTopBottom 四个附加属性指定元素相对于画布的位置,未指定位置的元素将出现在画布的左上角。当画布的大小被改变时,其子元素的位置也会相对于四个边的距离进行移动。
  • 使用 Panel.ZIndex 指定子元素的层级关系。
  • 子元素的部分或全部都可超过画布的边界,默认不会被裁剪,同时可以使用负坐标,即溢出的内容会显示在画布之外,这是因为默认 ClipToBounds=False,因此画布不需要指定大小。

示例

  1. 按照控件顺序堆叠显示,可指定层级关系

    <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>

    按照控件顺序堆叠显示