WPF,Silverlight与XAML读书笔记第二十五 - 控件之七 – 布局控件
说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。
在WPF中使用面板来进行布局,面板即一系列的布局控件。WPF中所有的布局控件 – 面板均派生自System.Windows.Controls命名空间下的System.Windows.Controls.Panel这个抽象类。
WPF中主要有如下五类面板:
-
Canvas
-
WrapPanel
-
Grid
-
StackPanel
-
DockPanel
下文将逐一介绍。
布局控件的通用属性
-
Margin:表示元素与其容器之间的距离,Margin属性使用4个逗号分隔的整数分别代表上,下,左,右4个间距。Grid中的元素单纯使用Margin来实现绝对定位,但是这样做会存在很大的缺点,即容器大小变化时其中的元素会发生重叠。如果想要绝对定位还是用Canvas来完成。
Canvas(WPF)
Canvas是最常见最基本的面板,其仅支持用显示坐标定位,当然这个坐标可以是相对于任何角的(而不仅仅是相对于左上角),这样就允许自由的在其中放置子控件。
Canvas的属性:
-
Width和Height:控制Canvas控件的长宽尺寸。
-
Top,Left ,Right与Bottom(附加属性):通过子元素上设置这些属性对子元素绝对定位。这四个附加属性每次只能使用两个,有4种组合,使子元素定位分别以Canvas的4个角为中心。
如下示例说明了这些属性的使用:
XAML:
1 <Canvas Margin="24,32,23,30" Name="canvas1"> 2 <Button Canvas.Left="20" Canvas.Top="36" Height="23" Name="button1" Width="75">Button_LT</Button> 3 <Button Canvas.Left="Auto" Canvas.Top="Auto" Height="23" Name="button2" Width="75" Canvas.Bottom="36" Canvas.Right="20">Button_RB</Button> 4 </Canvas>
效果图(运行时):
1.
2.
用作内部元素的附加属性是作为内部元素的外边距来使用的(并且如果内部元素有Margin属性,这两个属性的效果将会叠加)。如果没有设置这4个附加属性,内部元素会被放置在左上角(这是默认的布局定位位置)。
注意:不能同时设置两类附加属性。
这个原则前文有所提及,如果同时设置Canvas.Left和Canvas.Right属性,Right属性将被忽略。同理同时设置Canvas.Top与Canvas.Bottom,Canvas.Bottom将被忽略。
技巧:容器中元素的叠加处理
要很好的处理叠加,首先需要了解叠加规则,最一般的情况下,元素按添加到容器的顺序来处理叠加(对于XAML中体现在元素在文件中列出的顺序),后添加的元素将显示在先添加的元素之上。
另外使用RenderTransform渲染的元素会叠加在其它元素之上。
通过使用Panel(布局控件的抽象基类)的ZIndex附加属性可以控制子元素的Z顺序(层次顺序)。这个属性为整型,可以被设置为任意正整数或负整数正数,默认值为0。ZIndex较大的元素将呈现在ZIndex较小的元素之上。这个属性最终决定元素的层次顺序而不受其它因素(如添加顺序)的影响。另外很容易可以知道这个Z顺序可以使用程序代码在运行时动态调整,代码:
1 Canvas.SetZIndex(redButton, 0);
Canvas中的子元素布局属性的使用情况:
属性 |
是否可用 |
Margin |
部分可用,即4个margin值中的有两个与相应附加属性效果叠加。 |
HorizontalAlignment 和VerticalAlignment |
不能 |
LayoutTransform |
可用 |
StackPanel(WPF)
把其所有的子控件按纵向或横向堆叠起来,进行顺序排序。StackPanel没有定义任何附加属性所以使用起来比较简单。
StackPanel的属性
-
Orientation属性:System.Windows.Controls.Orientation类型,决定控件的子元素是横向排列还是纵向排列。默认值为Vertical。
提示:StackPanel的方向
当StackPanel的FlowDirection属性被设置为RightToLeft,Orientation属性设置为Horizontal时,StackPanel将从右向左排列子元素。
StackPanel中的子元素布局属性的使用情况:
属性 |
是否可用 |
Margin |
可用,控制子元素与StackPanel边缘及子元素之间的距离 |
HorizontalAlignment和VerticalAlignment |
部分可用,即排列方向上的对齐属性将被忽略。如:当Orientation= "Vertical",VerticalAlignment的设置就被忽略,同理当Orientation="Horizontal",HorizontalAlignment的设置将被忽略。 |
LayoutTransform |
可用。 |
另外需要说明LayoutTransform下某些变换与Stretch(拉伸)交互的一个问题。这不只针对StackPanel,所有情况下这两者交互时都遵守这样的原则,即当向一个被拉伸的元素应用RotateTransform或SkewTransform(均为LayoutTransform)时,只有当拉伸方向与RotateTransform或SkewTransform的变换后的角度平行或垂直时,拉伸的效果才显现出来。下面的例子清楚的说明了这个问题:
图1中,使用RotateTransform旋转了80度角所以拉伸效果不会出现
而图2中,RotateTransform旋转了90度,所以拉伸效果显示出来
附代码:
图1:
1 <StackPanel Width="136"> 2 <Button HorizontalAlignment="Stretch" Background="Red">ok</Button> 3 <Button HorizontalAlignment="Stretch" Background="Yellow"> 4 <Button.LayoutTransform> 5 <RotateTransform Angle="80"></RotateTransform> 6 </Button.LayoutTransform> 7 ok</Button> 8 <Button Background="Green">ok</Button> 9 <Button Background="Orange">ok</Button> 10 <Button Background="CadetBlue">ok</Button> 11 </StackPanel>
图2:将上面代码中粗体部分替换为:
1 <RotateTransform Angle="90"></RotateTransform>
WrapPanel(WPF)
类似于StackPanel,WrapPanel也会对子元素做栈处理,但其将所有子元素按顺序位置从左到右(上到下)排列,如果到达容器边缘时(即当前行或列空间不足时)将内容放置在下一行(列)达到一种换行(列)的效果。即WrapPanel提供横向或纵向排列的同时提供自动换行控制,而不像StackPanel即使右侧已处于一个不可见的区域仍然会向右侧显示而不是自动换行。处于这种特性WrapPanel对显示数目较大的Items很有用。
WrapPanel同样没有定义附加属性控制子元素位置,而是通过如下属性控制行为。
WrapPanel的属性:
-
Orientation:WrapPanel的排列方向,分横向(Horizontal)与纵向(Vertical)。默认值为Horizontal
注意:当FlowDirection设置为RightToLeft同时Orientation设置为Vertical时,子控件将在容器(WrapPanel)右侧自上而下显示。
-
FlowDirection:WrapPanel的环绕方向,有LeftToRight与RightToLeft两种值可选。当设置为RightToLeft时,当Orientation为Vertical时,WrapPanel自右向左换列,Orientation为Horizontal时,元素自右向左横向排列。
-
ItemHeight:定义了一个位于WrapPanel中子元素可以使用的统一的高度,如果子元素的Height比ItemHeight小,则填充方式取决于子元素的VerticalAlignment属性,反之如果比ItemHeight高则子元素会被截断。
-
ItemWidth:定义了一个子元素可以使用的统一的宽度,如果子元素的Width属性比ItemWidth小则子元素的HorizontalAlignment决定了子元素水平方向位置,如果比ItemHeight大则会被截断。
默认情况下,ItemHeight与ItemWidth为被设置或设置为Double.NaN。这种情况下,当Orientation设置为Vertical时,WrapPanel将每列宽度变为最宽的那个元素的宽度(注意子元素的Margin也计算在内),同理Orientation设置为Horizontal时,WrapPanel会把每一行的高度设置为最高元素的高度(同样子元素的Margin也计算在内)。这样默认情况下,不会出现由于ItemHeight或ItemWidth值太小导致元素被截断的情况。
技巧:怎样使WrapPanel的元素排列在一行或一列
通过设置Width或Height为Double.MaxValue或Double.PositiveInfinity可以分别实现对Orientation为Horizontal或Vertical情况下将元素排列在一行或一列。另外注意,由于Double.MaxValue与Double.PositiveInfinity均不受System.Double转换器支持,在XAML中,它们均需使用x:Static标记扩展完成。示例代码:
1 <StackPanel Width="{x:Static sys:Double.MaxValue}" Orientation="Horizontal">当然这句代码需要增加命名空间的引用:
1 xmlns:sys="clr-namespace:System;assembly=mscorlib"
WrapPanel中的子元素布局属性的使用情况:
属性 |
是否可用 |
Margin |
可用,如前文所述计算每行列默认宽高时,子元素Margin被计算在内。 |
HorizontalAlignment 和VerticalAlignment |
部分可用,Orientation="Vertical",HorizontalAlignment有效,但仅当子元素Height比ItemHeight小时,VerticalAlignment的设置才有效。同理当Orientation="Horizontal",VerticalAlignment有效,但仅当子元素Width小于ItemWidth时HorizontalAlignment的设置才有效。 |
LayoutTransform |
可用。 |
技巧:怎样实现WrapPanel子元素的选中
本来WrapPanel就最常用做Items控件的项目面板(ItemsPanel),所以实现这个功能,仅需将WrapPanel作为ListBox的默认面板即可,代码:
<ListBox> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
DockPanel(WPF)
DockPanel支持将子元素停靠在这个面板的某一边缘上,并拉伸元素以填充满全部宽度或高度(也支持让元素填充其他已停靠元素没有填充满的空间)。
DockPanel的属性
-
Dock(附加属性):子元素中设置这个属性来讲自身放置到上,下,左,右四个边缘之一,如果不设置默认停靠在左边缘。
-
LastChildFill:添加到DockPanel的最后一个元素是否填充所有剩余的空间,默认值为true(这时最后一个元素的Dock设置将被忽略),如果设置为false,最后一个元素将沿用其Dock设置(当然默认还是Left)。
DockPanel按XAML中添加子元素的顺序(再次提醒顺序很重要啊),来对其依次进行停靠操作,停靠时填充可能占据的最大空间(下文将介绍为什么会填充),下面的例子展示了这个问题,同时也展示了LastChildFill的使用。
XAML:
1 <DockPanel Margin="22,28,21,92" Name="dockPanel1" LastChildFill="True" > 2 <Button DockPanel.Dock="Top" Name="button1">Button_T</Button> 3 <Button Name="button2">Button_L</Button> 4 <Button Name="button3" DockPanel.Dock="Bottom">Button_B</Button> 5 <Button Name="button4" DockPanel.Dock="Right">Button_R</Button> 6 <Button Name="button5">Button_Center</Button> 7 </DockPanel>
效果图(设计时):
停靠时填充空间仍然是由于子元素的HorizontalAlignment与VerticalAlignment的默认值为Stretch,当然如果选择其它的对齐填充方式,元素将只停靠不填充。
DockPanel常用来设计顶层用户界面,尤其是停靠的元素是一些包含其它内容的面板时。容易想到的是菜单会停靠在窗口的上方,工具箱停靠在左边,状态栏停靠于底部而剩余内容填充满剩余中间的空间。
DockPanel功能比StackPanel强大,前者还可以轻松的模拟后者。如将LastChildFill设为false后,如果保持Dock的默认,DockPanel会向Orientation为Horizontal的StackPanel一样工作。如果将Dock全部设置为top,则DockPanel会向Orientation为Vertical的StackPanel那样工作。
DockPanel中的子元素布局属性的使用情况:
属性 |
是否可用 |
Margin |
可用,控制子元素与DockPanel边缘及子元素之间的距离 |
HorizontalAlignment 和VerticalAlignment |
部分可用,停靠方向上的对齐属性将被忽略。如:当Dock为Left与Right时,HorizontalAlignment的设置就被忽略,同理当Dock为Top或Bottom时,VerticalAlignment的设置将被忽略。 |
LayoutTransform |
可用。 |
Grid(WPF)
Grid是最通用的面板,其提供一个多行多列的环境来排列子元素(即子控件按行和列排放),并有很多属性来控制这些行列,行为上很像HTML中的Table。
Grid的属性:
-
ColumnDefinitions与RowDefinitions:分别义Grid控件的列与行,这两个属性在XAML中常以属性元素的方式设置。
-
Column与Row(附加属性):用于Grid内部的子元素,决定了元素位于Grid的哪一个列或行。
-
ColumnSpan与RowSpan(附加属性):分别定义了应用这个属性的子元素跨越的列数与行数。
Grid的使用:
首先,需要使用RowDefinitions与ColumnDefinitions属性添加所需数量的RowDefinition与ColumnDifinition元素实现行与列的定义。子元素使用Row与Column附加属性来确定其所在的单元格。这个两个附加属性基于0索引,默认值也是0,即如果不设置这两个属性,子元素将被放置在第一个单元格中。
与Canvas相同,位于同一个Grid单元格中的两个元素在布局上不会有任何交互,它们将按默认的Z顺序重叠显示。
RowDefinition与ColumnDifinition元素有Width与Height等属性,将它们设置为Auto,则可以是使行或列来适应其中元素的大小。这两个元素的这两个属性也需要做详细说明,它们的类型不同于FrameworkElement中的同名属性,是System.Windows.GridLength类型。正是这个原因可以使用如下3种方式设置RowDefinition与ColumnDefinition:
-
绝对尺寸(Absolute):这种设置正如其它Width和Height那样,使用一个设备无关的像素值。
-
自动尺寸(Auto):正如前文提到的,这将使Grid单元格适应其中的元素(Grid单元格的高度是最高元素的高度,宽度是最宽元素的宽度),这种设置适用于单元格内容是文本的情况,其会保证不同字体或不同文化的文字不会被截断。
-
比例尺寸(Proportional)(又称星号尺寸):可以将空间分为一定比例的区域,这些局域会随Grid的变化而成比例的放大或缩小。这种尺寸的表示方式是"系数+星号",系统将按系数来分割区域。比例尺寸不仅可以分割"完整的空间"也可分割"剩余空间",所谓剩余空间就是完整宽度(高度)减去使用绝对尺寸或自动尺寸定义的宽度(高度)。
下图的展示了一个Grid宽度的分割方式:
技巧:Grid单元格特殊效果的实现
Grid没有很复杂的机制支持像HTML表格单元格那样的样式设置。但是可以通过一些其它的方式来模拟:
模拟设置单元格的背景色:方法是在Grid单元格中放置一个Rectangle,并通过设置Fill属性来模拟背景色,默认情况下Rectangle会拉伸并填充单元格。 模拟设置单元格的内边距:将Grid单元格宽高设置为自动适应,并在子元素上设置Margin来模拟内边距效果。 模拟设置单元格边框:仍然是使用Rectangle,可以设置Stroke属性为某种颜色,如不使用Rectangle也可以直接使用Border元素。注意以上这些解决方案需要手动设置如Rectangle与Border的ZIndex,以使他们可以放置在所有元素的下面。
提示:用于调试环境的ShowGridLines属性
在调试环境下,将Grid的ShowGridLines属性设置为true,可以让Grid单元格边缘显示蓝黄色虚线。
在程序代码中使用GridLength
由于System.Windows.GridLengthConverter这个转换器的存在,XAML可以接受"100","Auto"和"2*"这样的字符串来设置RowDefinition或ColumnDefinition的Width与Height属性。而在C#代码中可以使用GridLength的构造函数来生成一个合适的属性值。
一个参数的构造函数会创建一个绝对尺寸的GridLength对象:
1 GridLength length = new GridLength(100);
两个参数的构造函数是最常用的,其第二个参数是一个GridUnitType枚举,用来区分要创建哪个类型的尺寸属性。如上面的代码等价于:
1 GridLength length = new GridLength(100, GridUnitType.Pixel);
另外自动尺寸和比例尺寸的属性可以按如下生成:
1 GridLength length = new GridLength(0, GridUnitType.Auto);
(这种情况下第一个参数将被忽略,其实最好的方式是使用GridLength.Auto属性表示自动尺寸,而不是用构造函数创建这样的对象。)
1 GridLength length = new GridLength(2, GridUnitType.Star);
(这种情况下第一个参数是比例尺寸中的系数,如上代码等价于XAML中的2*。)
使用GridSplitter动态改变单元格尺寸
Grid的另一个重要特性是可以使用相同命名空间下的GridSplitter类来构建可以动态改变行列尺寸的Grid。可以使用鼠标拖动,键盘控制或触控笔拖动GridSplitter来改变Grid单元格尺寸。
在Grid中添加GridSplitter正如添加其他子元素,也需要在GridSplitter中设置Grid.Row,Grid.Column,Grid.RowSpan或Grid.ColumnSpan这些附加属性。当然也可以添加不仅一个GridSplitter。
拖动GridSplitter时,受到直接影响的Grid单元格取决于GridSplitter的对齐值。这个过程中至少改变一个单元格的尺寸,其它单元格是移动还是改变尺寸取决于它们是否使用非比例尺寸(使用比例尺寸的单元格将改变尺寸,使用非比例尺寸的单元格将会移动)。
GridSplitter的HorizontalAlignment属性默认为Right,VerticalAlignment属性默认为Stretch,所以默认情况下GridSplitter在其单元格中停靠在右侧并拉伸填充满整个高度。(至少保证HorizontalAlignment或VerticalAlignment中有一个为Stretch,不然GridSplitter看起来像一个小点。)另外,虽然GridSplitter默认只位于一个单元格中,但水平拖动(或垂直拖动)时,整个列(或整个行,而不只是GridSplitter所在单元格)都会受到影响。所以,如果GridSplitter所在的单元格所在的行(或列)不是唯一的,则最好给其设置一个RowSpan(或ColumnSpan)来使其拉伸到整个Grid的高度(或宽度)。
注意:必须给GridSplitter显式设置一个Width(或Height),以保证其看起来是水平(或是垂直)的,这样这个控件才可用。
下面详细介绍GridSplitter的HorizontalAlignment 与VerticalAlignment属性的各种设置下,拖动GridSplitter对单元格的影响的情况。
首先介绍演示这个问题的程序(及代码):
代码:
1 <Grid ShowGridLines="True" Height="384" Width="561"> 2 <Grid.RowDefinitions> 3 <RowDefinition Height="36"/> 4 <RowDefinition/> 5 <RowDefinition/> 6 <RowDefinition/> 7 <RowDefinition/> 8 <RowDefinition Height="36"/> 9 </Grid.RowDefinitions> 10 <Grid.ColumnDefinitions> 11 <ColumnDefinition Width="36"/> 12 <ColumnDefinition/> 13 <ColumnDefinition/> 14 <ColumnDefinition/> 15 <ColumnDefinition/> 16 <ColumnDefinition Width="36"/> 17 </Grid.ColumnDefinitions> 18 19 <!--标签--> 20 <Label Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" > 21 <Label.LayoutTransform> 22 <RotateTransform Angle="90"></RotateTransform> 23 </Label.LayoutTransform> 24 Top 25 </Label> 26 <Label Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"> 27 <Label.LayoutTransform> 28 <RotateTransform Angle="90"></RotateTransform> 29 </Label.LayoutTransform> 30 Bottom 31 </Label> 32 <Label Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"> 33 <Label.LayoutTransform> 34 <RotateTransform Angle="90"></RotateTransform> 35 </Label.LayoutTransform> 36 Center 37 </Label> 38 <Label Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"> 39 <Label.LayoutTransform> 40 <RotateTransform Angle="90"></RotateTransform> 41 </Label.LayoutTransform> 42 Stretch 43 </Label> 44 <Label Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">Left</Label> 45 <Label Grid.Row="0" Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center">Right</Label> 46 <Label Grid.Row="0" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Center">Center</Label> 47 <Label Grid.Row="0" Grid.Column="4" VerticalAlignment="Center" HorizontalAlignment="Center">Stretch</Label> 48 49 <!--第一排--> 50 <GridSplitter Grid.Row="1" Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="10" Height="10" Background="Brown"></GridSplitter> 51 <GridSplitter Grid.Row="1" Grid.Column="2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="10" Height="10" Background="Coral"></GridSplitter> 52 <GridSplitter Grid.Row="1" Grid.Column="3" VerticalAlignment="Top" HorizontalAlignment="Center" Width="20" Height="5" Background="Orange"></GridSplitter> 53 <GridSplitter Grid.Row="1" Grid.Column="4" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="3" Background="Chartreuse"></GridSplitter> 54 <!--第二排--> 55 <GridSplitter Grid.Row="2" Grid.Column="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="10" Height="10" Background="DimGray"></GridSplitter> 56 <GridSplitter Grid.Row="2" Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="10" Height="10" Background="Goldenrod"></GridSplitter> 57 <GridSplitter Grid.Row="2" Grid.Column="3" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="20" Height="5" Background="Tomato"></GridSplitter> 58 <GridSplitter Grid.Row="2" Grid.Column="4" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="3" Background="YellowGreen"></GridSplitter> 59 <!--第三排--> 60 <GridSplitter Grid.Row="3" Grid.Column="1" Height="20" Width="5" VerticalAlignment="Center" HorizontalAlignment="Left" Background="CadetBlue"></GridSplitter> 61 <GridSplitter Grid.Row="3" Grid.Column="2" Height="20" Width="5" VerticalAlignment="Center" HorizontalAlignment="Right" Background="BurlyWood"></GridSplitter> 62 <GridSplitter Grid.Row="3" Grid.Column="3" Width="10" Height="10" VerticalAlignment="Center" HorizontalAlignment="Stretch" Background="Chocolate"></GridSplitter> 63 <GridSplitter Grid.Row="3" Grid.Column="4" Height="3" VerticalAlignment="Center" HorizontalAlignment="Stretch" Background="Green"></GridSplitter> 64 <!--第四排--> 65 <GridSplitter Grid.Row="4" Grid.Column="1" Width="3" VerticalAlignment="Stretch" HorizontalAlignment="Left" Background="Fuchsia"></GridSplitter> 66 <GridSplitter Grid.Row="4" Grid.Column="2" Width="3" VerticalAlignment="Stretch" HorizontalAlignment="Right" Background="DarkCyan"></GridSplitter> 67 <GridSplitter Grid.Row="4" Grid.Column="3" Width="3" VerticalAlignment="Stretch" HorizontalAlignment="Center" Background="Red"></GridSplitter> 68 <GridSplitter Grid.Row="4" Grid.Column="4" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Azure"></GridSplitter> 69 70 </Grid>
运行时效果图:
如图,左侧表示VerticalAlignment的设置,上侧表示HorizontalAlignment的设置,对应上图,下面的表格介绍了各种设置组合影响单元格的效果:
上面表格使用不同颜色区分了几种对单元格影响的情形,下面在按尺寸定义方式(绝对尺寸还是比例尺寸)来继续细分这几种情况。
1. 当前单元格与左侧单元格受影响(列宽度受影响)
2. 当前单元格与上方单元格受影响(列宽度受影响)
3. 左侧单元格与右侧单元格受影响(列宽度受影响)
4. 当前单元格与上方单元格受影响(行高度受影响)
5. 当前单元格与下方单元格受影响(行高度受影响)
6. 上方单元格与下方单元格受影响(行高度受影响)
7. 宽度大于高度从而行高受影响情况
高度大于宽度从而列宽受影响的情况
有两个属性可以显式,独立控制改变尺寸的行为与GridSplitter对齐的方向。
ResizeDirection(类型是GridResizeDirection)独立显式控制尺寸改变方向,ResizeBehavior(类型是GridResizeBehavior)独立显示控制尺寸改变行为。 ResizeDirection只在可以在左右上下这些方向上拖动GridSplitter时(如前文图中右下角那个GridSplitter),这个属性才有效,其默认设置为Auto,可以被设置为Rows或Columns。
BesizeBehavior默认设置为BasedOnAlignment,表示按照GridSplitter定义的Alignment方式进行定位,按此定位来确定拖动时影响单元格的行为。这个属性可选值还有PreviousAndCurrent、CurrentAndNext或PreviousAndNext,用来控制哪两行或哪两列应该在拖动时受到影响。这几个属性具体含义如下:
- PreviousAndCurrent: 对于水平 GridSplitter,将在为 GridSplitter 指定的行与该行上面的一行之间重新分配空间。对于垂直 GridSplitter,将在为 GridSplitter 指定的列与该列左侧的一列之间重新分配空间。
- CurrentAndNext: 对于水平 GridSplitter,将在为 GridSplitter 指定的行与该行下面的一行之间重新分配空间。对于垂直 GridSplitter,将在为 GridSplitter 指定的列与该列右侧的一列之间重新分配空间。
- PreviousAndNext: 对于水平 GridSplitter,将在为 GridSplitter 指定的行上面的各行与该行下面的各行之间重新分配空间。对于垂直 GridSplitter,将在为 GridSplitter 指定的列左侧的各列与该列右侧的各列之间重新分配空间。
提示:GridSplitter的最佳使用方式是放置在一个单独的Grid单元格中,且将这个单元格的Width或Height设置为Auto。如果将其与其它元素放置于一个单元格中,一定要通过设置ZIndex保证GridSplitter在其它元素上方。
Grid行列尺寸的其它设置
RowDefinition与ColumnDefinition都有一个SharedSizeGroup属性,具有此属性且属性值(此值即为组名,大小写敏感)相同的2个或多个行或列无论在静止或动态改变时高度或宽度都会保持一致。
由于尺寸组(Size Group)可以在多个Grid间共享,所以为了保证尺寸组是在一个Grid内被应用,可以给Grid指定IsSharedSizeScope="True"(IsSharedSizeScope为Grid的依赖属性)。而且这种做法是推荐的。示例代码如下(部分):
1 <Grid IsSharedSizeScope="True"> 2 <Grid.ColumnDefinitions> 3 <ColumnDefinition Width="Auto" SharedSizeGroup="myGroup"/> 4 <ColumnDefinition /> 5 <ColumnDefinition SharedSizeGroup="myGroup"/> 6 </Grid.ColumnDefinitions> 7 </Grid>
另外,IsSharedSizeScope也可以是在非Grid父元素上使用的附加属性,直接给出代码示例:
1 <StackPanel Grid.IsSharedSizeScope="True"> 2 <Grid> 3 <!-- ... --> 4 </Grid> 5 <Grid> 6 <!-- ... --> 7 </Grid> 8 <!-- 以上Grid将共享SharedSizeGroup属性 --> 9 </StackPanel>
Grid与其它布局(面板)控件
Grid是一种很灵活的布局控件,可以模拟几乎其它所有布局(面板)控件。下面举几个例子:
- Grid来模拟Canvas
设置Grid为一行一列,且其中所有子元素的HorizontalAlignment与VerticalAlignment属性为除了Stretch以外的值。这样将子元素添加到这个Grid中表现就如同添加到一个Canvas中。
另外: Grid中HorizontalAlignment=Left等价于Canvas中Canvas.Left=0,VerticalAlignment=Top等价于Canvas.Top=0;Grid的HorizontalAlignment=Right等价于Canvas.Right=0,VerticalAlignment=Bottom等价于Canvas.Bottom=0。另外设置Grid中每个子元素的Margin属性与设置Canvas中每个元素的Margin附加属性等效。 - Grid模拟StackPanel
一个单列多行的Grid在行高被设为自动时,其表现就如一个垂直放置的StackPanel。同理当一个单行多列的Grid列宽被设为自动时,表现就如一个水平放置的StackPanel。 - Grid模拟DockPanel
当对边缘位置的元素,如上下边元素使用ColumnSpan,左右边元素使用RowSpan将分别会产生停靠并拉伸到Grid边缘的效果。
Grid中的子元素布局属性的使用情况:
属性 |
是否可用 |
Margin |
可用,控制子元素与单元格边缘之间(也包括元素间)的距离 |
HorizontalAlignment 和VerticalAlignment |
可用,并且在两个方向上均可用。除非在一个自动尺寸单元格中某个子元素没有额外的空间。大多数情况下元素可以伸展并填充单元格。(填充是由于HorizontalAlignment与VerticalAlignment的默认值均为Stretch) |
LayoutTransform |
可用。 注意:如果有一个元素因为缩放而跑到单元格边界外去,其会被截断。 |
注意:关于Grid控件模拟其它控件,在处理未知数量的子元素时(尤其像Itmes控件的Items面板),StackPanel和WrapPanel要更好。另外模拟DockPanel的情况,由于向Grid添加行列时还要随时调整RowPanel和ColumnPanel来模拟停靠假象,所以在复杂情况下还是应该使用DockPanel。
最后介绍一些原始面板,它们只可用于控件内部,用于重设内建控件样式或用于创建自定义控件。所有这些面板除ToolBarTray位于System.Windows.Controls命名空间外都位于System.Windows.Controls.Primitives命名空间。
TabPanel(WPF)
实际使用中,这类控件往往使用如下方式嵌套:
虽然TabControl可以包含多个TabItem实现效果,但最好仍然在TabItem外侧套一个TabPanel,使TabControl更容易控制其中TabItem的位置(如果要实现多行TabItem,也需要使用TabPanel对TabItem进行管理)。在TabItem中可以放置任意元素。TabPanel类似WarpPanel,用于在默认样式的TabControl中对TabItem进行布局。TablePanel仅支持从左到右(仅当FlowDirection=LeftToRight)排列其中元素(TabItem),从上至下来换行(这点与WrapPanel不同)。类似于Windows中一些组件标签选项卡的设计,当换行发生时,会平均拉伸TabItem的Header来占据面板的全部宽度。
对于TabControl详见Items控件-选择器控件部分。
ToolBarOverflowPanel (WPF)
ToolBarOverflowPanel也是简化的WrapPanel,仅支持从左到右的排列与从上至下的换行,通常用于默认样式的ToolBar,用于显示无法在主区域显示(即内容溢出的项)的工具条项。另外其有一个类似于WrapPanel的Padding属性功能的WrapWidth属性。
通常应考虑使用WrapPanel代替ToolBarOverflowPanel。
ToolBarTray (WPF)
ToolBarTray仅支持ToolBar作为子元素,否则会抛出InvalidOperationException。ToolBarTray以水平方式排列其中的ToolBar,类似于现有Windows应用程序,也可以拖动ToolBarTray中的每一个ToolBar来产生新行,压缩或增加相邻ToolBar间距离。
注意,ToolBarTray主要用于对ToolBar布局,而ToolBarOverflowPanel主要用于对ToolBar中的内容溢出的Item进行布局。
UniformGrid (WPF)
这个面板是由Grid简化而来,所有行和列的大小都固定设置为*。有两个float类型属性分别设置行数和列数。每个"单元格"中只能有一个子元素。
如果不显式设置行列数,UniformGrid会自动给出恰当的值。如,有5~9个元素时会自动生成3x3个格子,有10~16个元素会自动选用4x4个格子,以此类推。
VirtualizingStackPanel (WPF)
VirtualizingStackPanel派生自VirtualizingPanel抽象类,默认用于LixtBox内部。该面板工作方式类似于StackPanel,但最大的不同是但使用数据绑定时它会临时抛弃一部分不在显示范围内的项来提高性能。所以处理大量子元素时,VirtualizingStackPanel是最好的面板。
本文完
参考:
《WPF揭秘》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异