WPF布局

在WPF中非常抵制基于坐标的布局,而是注重创造更灵活的布局,使布局能够适应内容的变化、不同的语言以及各种窗口尺寸。

布局原则

  • 不应显式设定元素(如控件)的尺寸。元素应当可以改变尺寸以适合它们的内容。可通过设置最大和最小尺寸来限制可以接受的控件尺寸范围
  • 不应使用屏幕坐标指定元素的位置。元素应当由它们的容器根据它们的尺寸/顺序以及(可选的)其他特定于具体布局容器的信息进行排列。如果需要在元素之间添加空白空间。可使用Margin
  • 布局容器的子元素“共享”可用的空间。如果空间允许,布局容器会根据每个元素的内容尽可能为元素设置更合理的尺寸。它们还会向一个或多个元素分配多余的空间
  • 可嵌套的布局容器。布局容器之间可以进行嵌套组合

布局过程

  WPF 布局包括两个阶段:测量(measure)阶段和排列(arrange)阶段。在测量阶段,容器遍历所有子元素,并询问子元素它们所期望的尺寸。在排列阶段,容器在合适的位置放置子元素。

布局容器

  所有WPF布局容器都是派生自System.Windows.Controls.Panel抽象类的面板。

Panel类的层次结构

Panel类还包含几个内部属性,如果希望创建自己的容器,就可以使用它们。最特别的是,可重写继承自FrameworkElement类的 MeasureOverride()和ArangeOverride()方法,以修改当组织子元素时面板处理测量阶段和排列阶段的方式。

核心布局面板
名称 说明
StackPanel 在水平或垂直的堆栈中放置元素。这个布局容器通常用于更大/更复杂窗口中的一些小区域
WrapPanl 在一些列可换行的行中放置元素
DockPanel 根据容器的整个边界调整元素
Grid 根据不可见的表格在行和列中排列元素,这是最灵活、最常见的容器之一
UniformGrid 在不可见但是强制所有单元格具有相同尺寸的表中放置元素,整个布局容器不常用
Canvas 使用固定坐标绝对定位元素

StackPanel

布局属性
名称 说明
HorizontalAlignment 当水平方向上有额外的空间时,该属性决定了子元素在布局容器中如何定位。可选用 Center、Left、Right或 Stretch 等属性值
VerticalAlignment 当垂直方向上有额外的空间时,该属性决定了子元素在布局容器控件中如何定位。可选用 Center、Top、Bottom或Stretch 等属性值
Margin 该属性用于在元索的周围添加一定的空间
MinWidth和MinHeight 最小尺寸
MaxWidth和MaxHeight 最大尺寸
Width和Height 显示地设置元素的尺寸

Border

  Border控件不是布局面板,而是非常便于使用的元素,经常与布局面板一起使用。

Border类的属性
名称 说明
Background 使用Brush对象设置边框中所有内容后面的背景
BorderBrush和BorderThickness 使用Brush对象设置位于Border对象边缘的边框的颜色,并设置边框的宽度
CornerRadius 该属性可使边框具有精致的圆角。CornerRadius的值越大,圆角效果就越明显
Padding 该属性在边框和内部的内容之间添加空间(与此相对,Margin属性在边框之外添加空间)

WrapPanel

  WrapPanel 面板在可能的空间中,以一次一行或一列的方式布置控件。默认情况下WrapPanel.Orientation 属性设置为 Horizontal;控件从左向右进行排列,再在下一行中排列。但可将 WrapPanel.Orientation 属性设置为 Vertical,从而在多个列中放置元素。

与StackPanel面板类似,WrapPanel面板实际上主要用来控制用户界面中一小部分的布局细节,并非用于控制整个窗口布局

DockPanel

  DockPanel面板沿着一条外边缘来拉伸所包含的控件,通过锚点停靠的方式设置子元素的位置

DockPanel常见属性
名称 说明
DockPanel.Dock 子元素的附加属性,设置子元素在DockPanel的停靠位置
LastChildFill 设置DockPanel中最后一个元素是否填充DockPanel剩余控件(默认为true)

Grid

  Grid面板将元素分隔到不可见的行列网格中。尽管可在一个单元格中放置多个元素(这时这些元素会相互重叠),但在每个单元格中只放置一个元素通常更合理

Grid常见属性
名称 说明
RowDefinitions和ColumnDefinitions 定义Grid中的行列集合,行列需要在此集合内声明
RowDefinition和ColumnDefinition 用于定义一行或一列
Grid.Row和Grid.Column 子元素的附加属性,设置子元素在Grid中的行列位置(索引从0开始)
Grid.RowSpan和Grid.ColumnSpan 子元素的附加属性,可设置子元素跨行或跨列
ShowGridLines 用于显示Grid网格中的行列线(默认false)
GridSplitter Grid分割线,可用于分割窗口
Grid.IsSharedSizeScope 用于共享尺寸组
调整行和列

Grid面板支持以下三种设置尺寸的方式:

  • 绝对设置尺寸方式。使用设备无关单位准确地设置尺寸

    1
    2
    <!-- 设置行高为100 -->
    <RowDefinition Height="100"/>
  • 自动设置尺寸方式。每行或每列的尺寸刚好满足需要

    1
    2
    <!-- 设置行高自适应高度,由内容撑开 -->
    <RowDefinition Height="auto"/>
  • 按比例设置尺寸方式。按比例将控件分割到一组行和列中

    1
    2
    3
    <!-- 按比例划分Grid剩下空间,第二行是第一行的2倍高度 -->
    <RowDefinition Height="*"/>
    <RowDefinition Height="2*"/>
分割窗口

  每个 Windows用户都见过分割条–能将窗口的一部分与另一部分分离的可拖动分割器例如,当使用 Windows 资源管理器时,会看到一系列文件夹(在左边)和一系列文件(在右边)。可拖动它们之间的分割条来确定每部分占据窗口的比例。

1
2
3
4
5
6
7
8
9
10
11
12
13
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--左侧菜单-->
<Grid Grid.Column="0"></Grid>
<!--分割线-->
<GridSplitter Grid.Column="1" Width="3" ShowsPreview="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
<!--右侧内容-->
<Grid Grid.Column="2"></Grid>
</Grid>
共享尺寸组

  共享尺寸组的目标是保持用户界面独立部分的一致性。例如,可能希望改变一列的尺寸以适应其内容,并改变另一列的尺寸使其与前面一列改变后的尺寸相匹配。然而,共享尺寸组的真正优点是使独立的 Grid 控件具有相同的比例。

  • 默认情况,尽管每一列都设置了Auto,但是在同一列中都是以最长的一列作为当前列的宽度,不会影响其他列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <StackPanel>
    <Grid>
    <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="auto"/>
    <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>
    <Button Grid.Row="0" Grid.Column="0" Content="One"/>
    <Button Grid.Row="0" Grid.Column="1" Content="Two"/>
    <Button Grid.Row="1" Grid.Column="0" Content="Three (Which is Longer)"/>
    <Button Grid.Row="1" Grid.Column="1" Content="Four"/>
    </Grid>
    </StackPanel>
  • 共享尺寸组,开启Grid.IsSharedSizeScope="True"共享尺寸组,为需要共享尺寸的列设置相同的组SharedSizeGroup="Group1"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <StackPanel>
    <Grid Grid.IsSharedSizeScope="True">
    <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="auto" SharedSizeGroup="Group1"/>
    <ColumnDefinition Width="auto" SharedSizeGroup="Group1"/>
    </Grid.ColumnDefinitions>
    <Button Grid.Row="0" Grid.Column="0" Content="One"/>
    <Button Grid.Row="0" Grid.Column="1" Content="Two"/>
    <Button Grid.Row="1" Grid.Column="0" Content="Three (Which is Longer)"/>
    <Button Grid.Row="1" Grid.Column="1" Content="Four"/>
    </Grid>
    </StackPanel>

UniformGrid

  有一种网格不遵循前面讨论的所有原则–UniformGrid 面板。与 Grid 面板不同,UniformGrid 面板不需要(甚至不支持)预先定义的列和行。相反,通过简单地设置 Rows 和Columns属性来设置其尺寸。每个单元格始终具有相同的大小,因为可用的空间被均分。最后元素根据定义的顺序被放置到适当的单元格中。UniformGrid面板中没有 Row 和 Column 附加属性,也没有空白单元格。

UniformGrid常用属性
名称 说明
Rows 设置面板的行数
Columns 设置面板的列数

Canvas

  Canvas面板允许使用精确的坐标放置元素,如果设计数据驱动的富窗体和标准对话框,这并非好的选择;但如果需要构建其他一些不同的内容(例如,为图形工具创建绘图表面),Canvas面板可能是个有用的工具。Canvas面板还是最轻量级的布局容器。这是因为Canvas面板没有包含任何复杂的布局逻辑,用以改变其子元素的首选尺寸。Canvas面板只是在值定的位置放置其子元素,并且子元素具有所希望的精确尺寸。

名称 说明
Canvas.Left 子元素附加属性,用于设置子元素相对于Canvas左侧的坐标
Canvas.Right 子元素附加属性,用于设置子元素相对于Canvas右侧的坐标
Canvas.Top 子元素附加属性,用于设置子元素相对于Canvas上边的坐标
Canvas.Bottom 子元素附加属性,用于设置子元素相对于Canvas下边的坐标
Canvas.ZIndex 子元素附加属性,用于设置子元素层叠关系,值越大越靠上

InkCanvas

  InkCanvas 元素的主要目的是用于接收手写笔输入。手写笔是一种在平板 PC中使用的类似于钢笔的输入设备,然而,InkCanvas 元素同时也可使用鼠标进行工作,就像使用手写笔一样。因此,用户可使用鼠标在 InkCanvas 元素上绘制线条,或者选择以及操作 InkCanvas 中的元素。

  InkCanyas 元素实际上包含两个子内容集合。一个是为人熟知的 Children 集合,它保存任意元素,就像 Canvas 面板一样。每个子元素可根据 Top、Left、Bottom 和 Right 属性进行定位。另一个是 Strokes 集合,它保存 System.Windows.Ink.Stroke 对象,该对象表示用户在 InkCanvas元素上绘制的图形输入。用户绘制的每条直线或曲线都变成独立的 Stroke对象。得益于这两个集合,可使用 InkCanvas 让用户使用存储在 Strokes集合中的笔画(stroke)为保存在 Children 集合中的内容添加注释。

  根据为InkCanvas.EditingMode属性设置的值,可以采用截然不同的方式使用InkCanvas元素

InkCanvasEditingMode枚举值
名称 说明
Ink InkCanvas元素允许用户绘制批注,这是默认模式。但用户用鼠标或手写笔绘图时,会绘制笔画
GestureOnly InkCanvas元素不允许用户绘制笔画标注,党费关注预先定义的特定姿势(例如在某个方向拖动手写笔或涂画内容)
InkAndGesture InkCanvas元素允许用户绘制笔画批注,也可以识别预先定义的姿势
EraseByStroke 当单击笔画时,InkCanvas元素会擦除笔画
EraseByPoint 但单击笔画时,InkCanvas元素会擦除笔画中被单击的部分(笔画上的一个点)
Select InkCanvas面板允许用户选择保存在Children集合中的元素。要选择一个元素,用户必须单击该元素或拖动“索套”选择该元素。一旦选择一个元素,就可以移动该元素、改变其尺寸或将其删除
None InkCanvas元素忽略鼠标和手写笔输入