理解WPF布局
WPF采用了流布局。流布局允许控件变大,并且能挤开其他控件。
WPF布局哲学
在WPF中,容器决定了布局。尽管有几种容器,WPF窗口遵守几个关键的原则:
- 元素(诸如控件)不应显式设置尺寸
- 元素不指明定位坐标
- 布局容器在子元素之间共享可用空间
- 布局容器能被嵌套
布局过程
WPF布局分二阶段:测量阶段和布置阶段。测量阶段,容器遍历它的所有子元素,要求它们提供它们的优先尺寸。布置阶段,容器摆放子元素到合适的位置。
有时容器不够大不能容纳元素。在这种情况下,为了适应可见区域,容器必须截断冒犯元素。如你所见,通过设置一个最小窗口尺寸,经常能避免这情况。
布局容器
所有WPF布局容器都派生自System.Windows.Controls.Panel抽象类。Panel类增加了几个成员,包括三个公开的属性。
名字 | 描述 |
Background | Brush,如果要接收鼠标事件,必须设置此属性非空。 |
Children | 是面板中项集。这是第一层项目—换句话说,这些项本身可能包含更多项。 |
IsItemsHost | 一个布尔值。如果为真,面板被用于显示ItemsControl控件的项集。 |
Panel本身只是一个起点。
核心布局面板包括:StackPanel、WrapPanel、DockPanel、Grid、UniformGrid
专用的布局面板包括:TabPanel、ToolbarPanel、ToolbarOverflowPanel、VirtualizingStackPanel
StackPanel
栈面板是最简单的布局容器之一,它简单地以单行或单列的方式堆垛它的子元素。
例如,考虑这个窗口,它包含有四个按钮的栈:
<StackPanel> <Label>A Button Stack</Label> <Button>Button 1</Button> <Button>Button 2</Button> <Button>Button 3</Button> <Button>Button 4</Button> </StackPanel>
默认情况下,StackPanel自顶到底排列元素。
通过设置Orientation属性,可以水平排列控件:
<StackPanel Orientation="Horizontal">
布局属性
布局属性包括HorizontalAlignment,VerticalAlignment;Margin; MinWidth, MinHeight, MaxWidth, MaxHeight; Width, Height。
名字 | 描述 |
HorizontalAlignment | |
VerticalAlignment | |
Margin | |
MinWidth、MinHeight | |
MaxWidth、MaxHeight | |
Width、Height |
所有这些属性都在FrameworkElement类中定义。所以,所有的图形部件都可以使用这些属性。
Alignment
见59页
Margin
见60页
最小、最大、和显式尺寸
见62页
Border
Border不是一个布局面板,但是常与布局面板一起使用。
Border类非常简单。它取单个的嵌套内容(这是经常一个布局面板)并且添加一个背景或一个围绕的边界。
Border类的属性:
名字 | 描述 |
Background | 设置内容背后的背景依靠Brush对象。你能使用一个实体颜色或更奇异的什么。 |
BorderBrush、BorderThickness | 设置出现在Border对象的边界的边界的颜色,使用一个刷子对象,并且设置边界的宽度,各自地。为显示一个边界,你必须二个属性都设置。 |
CornerRadius | 边界拐角的倒圆角半径 |
Padding | 边界与内容之间的距离,内边距。 |
这里有一个倒圆角的边界,环绕一个栈面板,栈面板包括一组按钮:
<Border Margin="5" Padding="5" Background="LightYellow" BorderBrush="SteelBlue" BorderThickness="3,5,3,5" CornerRadius="3" VerticalAlignment="Top"> <StackPanel> <Button Margin="3">One</Button> <Button Margin="3">Two</Button> <Button Margin="3">Three</Button> </StackPanel> </Border>
注意:Border是一个装饰者,所有的装饰者都派生自System.Windows.Controls.Decorator类。
WrapPanel
WrapPanel.Orientation属性默认为Horizontal,子元素按行的布置。但是,设置Orientation为Vertical后,则按列布置。
DockPanel
通过一个附加属性Dock,子元素可以选择他们希望停靠的边。这个属性能被设置为Left,Right,Top,或Bottom。
DockPanel.Dock="Top"
DockPanel的LastChildFill属性表示其最后一个子元素将被拉伸充满容器:
LastChildFill="True"
子元素定义的顺序显著地影响布局。
嵌套布局容器
创造一个标准对话框,在右下角落带有一个OK和Cancel按钮,剩余部分是一个大的内容区域。
这个对话框将用几种不同的方式实现。这是用嵌套容器方法的标记:
<DockPanel LastChildFill="True"> <StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal"> <Button Margin="10,10,2,10" Padding="3">OK</Button> <Button Margin="2,10,10,10" Padding="3">Cancel</Button> </StackPanel> <TextBox DockPanel.Dock="Top" Margin="10">This is a test.</TextBox> </DockPanel>
Grid
每个单元格通常只放一个元素。当然,这个元素可以是另一个布局容器。
创造一个基于网格的布局是一个两步过程。首先,你选择列数和行数。其次你分配合适的行和列到每个包含元素,这样放置它在恰好正确地点。
提示:Grid的ShowGridLines为真表示在设计时显示网格线。
使用Grid需要先定义行和列,例如,这是一个两行、三列的网格:
<Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> ... </Grid>
在这个例子中,没有为行和列定义提供信息。网格控件均分行和列。每个单元格的尺寸相同。
为放置元素到一个单元格内,你使用Row和Column附加属性。这些属性都取基于零的索引编号。例如:
<Grid ShowGridLines="True"> ... <Button Grid.Row="0" Grid.Column="0">Top Left</Button> <Button Grid.Row="0" Grid.Column="1">Middle Left</Button> <Button Grid.Row="1" Grid.Column="2">Bottom Right</Button> <Button Grid.Row="1" Grid.Column="1">Bottom Middle</Button> </Grid>
每个元素必须被显式地放置到它的单元格内。这允许你放置一个以上元素到同一个单元格内,或留下某些单元格为空白。它也意味着你能不按顺序声明你的元素,就像在这个例子中最后二个按钮。
如果控件位于Grid的第一行,可以不指定Grid.Row。如果控件位于Grid的第一列,可以不指定Grid.Column。
注意:如果网格有一个以上行和列,你必须使用RowDefinition和ColumnDefinition对象显式地定义你的行和列。
精确调整行和列
Grid支持三种尺寸策略:
绝对尺寸:
<ColumnDefinition Width="100"></ColumnDefinition>
自动尺寸,是可以容纳内容的最小尺寸:
<ColumnDefinition Width="Auto"></ColumnDefinition>
比例尺寸,一个星号代表一份:
<ColumnDefinition Width="*"></ColumnDefinition>
如果你混合使用成比例尺寸模式和其他尺寸模式,成比例尺寸行或列获得剩余空间。
星号之前放置一个数字代表权重,可以解读为份数,下例表示第二行是第一行的2倍高:
<RowDefinition Height="*"></RowDefinition> <RowDefinition Height="2*"></RowDefinition>
注意:以编程方式与ColumnDefinition和RowDefinition对象互相作用是容易的。你只需知道Width和Height属性是GridLength对象。为使GridLength代表一个指定的尺寸,只要传递合适的值到GridLength构造函数。为使GridLength代表一个成比例的(*)尺寸,传递数目到GridLength构造函数,并传递GridUnitType.Star作为构造函数的第二个参数。为指明自动的尺寸,使用静态属性GridLength.Auto。
使用这些尺寸模式,简单对话框的例子:
<Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <TextBox Margin="10" Grid.Row="0">This is a test.</TextBox> <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal"> <Button Margin="10,10,2,10" Padding="3">OK</Button> <Button Margin="2,10,10,10" Padding="3">Cancel</Button> </StackPanel> </Grid>
提示:Grid没有声明任何列。这是一个快捷方式,表示你的网格只使用一列,并且那列是成比例地尺寸(因而它充满网格的全宽度)。
跨行和跨列
你也能使用二个附加属性使一个元素拉伸占据几个单元格:RowSpan和ColumnSpan。这两个属性取元素要占用的行或列数。
这个按钮将占据第一行的第一和第二单元格的所有可用空间
<Button Grid.Row="0" Grid.Column="0" Grid.RowSpan="2">Span Button</Button>
这个按钮将占据跨二列和二行合计四单元格
<Button Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2"> Span Button</Button>
使用跨列重写前面简单对话框的例子:
<Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBox Margin="10" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"> This is a test.</TextBox> <Button Margin="10,10,2,10" Padding="3" Grid.Row="1" Grid.Column="1">OK</Button> <Button Margin="2,10,10,10" Padding="3" Grid.Row="1" Grid.Column="2">Cancel</Button> </Grid>
注意,按钮的地址为合并单元格左上角单元格的地址。
分割窗口
定义分隔栏应该遵循几个法则:
- 保留一个专用于GridSplitter的列或行,设置行Height或列Width值为Auto。
- GridSplitter始终修改整行或整列的尺寸(不是单个的单元格)。你应该用RowSpan或ColumnSpan属性使GridSplitter横跨整行或整列而不局限于单个的单元格。
- GridSplitter的Width设置为固定尺寸如10,长度方向Alignment设置为Stretch,分隔方向Alignment设置为Center。
下面是应用这些法则的一个例子,这个例子是一个垂直分隔栏:
<Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="100"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition MinWidth="50"></ColumnDefinition> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="0" Margin="3">Left</Button> <Button Grid.Row="1" Grid.Column="0" Margin="3">Left</Button> <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Width="10" VerticalAlignment="Stretch" HorizontalAlignment="Center" ShowsPreview="False"> </GridSplitter> <Button Grid.Row="0" Grid.Column="2" Margin="3">Right</Button> <Button Grid.Row="1" Grid.Column="2" Margin="3">Right</Button> </Grid>
代码中GridSplitter出现了ShowPreview属性。GridSplitter还有没有出现在代码中的DragIncrement属性。