StackPanel用于水平或垂直堆叠子元素。也就是说,StackPanel同样也有一个Children属性,而Children集合中的元素呈现在界面上时,只能是按水平或垂直方式布局。我们先来看看它的结构定义:
public class StackPanel : Panel, IScrollInfo, IStackMeasure
{
public static readonly DependencyProperty OrientationProperty;
public StackPanel();
public double HorizontalOffset { get; }
public double ViewportHeight { get; }
public double ViewportWidth { get; }
public double ExtentHeight { get; }
public double ExtentWidth { get; }
public bool CanVerticallyScroll { get; set; }
public bool CanHorizontallyScroll { get; set; }
public Orientation Orientation { get; set; }
public double VerticalOffset { get; }
public ScrollViewer ScrollOwner { get; set; }
protected internal override Orientation LogicalOrientation { get; }
protected internal override bool HasLogicalOrientation { get; }
public void LineDown();
public void LineLeft();
public void LineRight();
public void LineUp();
public Rect MakeVisible(Visual visual, Rect rectangle);
public void MouseWheelDown();
public void MouseWheelLeft();
public void MouseWheelRight();
public void MouseWheelUp();
public void PageDown();
public void PageLeft();
public void PageRight();
public void PageUp();
public void SetHorizontalOffset(double offset);
public void SetVerticalOffset(double offset);
protected override Size ArrangeOverride(Size arrangeSize);
protected override Size MeasureOverride(Size constraint);
}
StackPanel提供了一些属性和方法,最常用的是Orientation枚举属性,用于设置子控件在StackPanel内部的排列方式,分别是水平排列(Horizontal)和垂直排列(Vertical)。默认值是垂直排列(Vertical)。
一、水平排列
<StackPanel Orientation="Vertical">
<Button Content="WPF中文网1" Margin="20" />
<Button Content="WPF中文网2" Margin="20" />
<Button Content="WPF中文网3" Margin="20" />
<Button Content="WPF中文网4" Margin="20" />
</StackPanel>

请注意,当StackPanel子元素处于垂直排列时,此时子元素的宽度默认与StakcPanel的宽度保持一致,但是子元素的高度是与其自身的高度自适应显示。
二、垂直排列
<StackPanel Orientation="Horizontal">
<Button Content="WPF中文网1" Margin="20" />
<Button Content="WPF中文网2" Margin="20" />
<Button Content="WPF中文网3" Margin="20" />
<Button Content="WPF中文网4" Margin="20" />
</StackPanel>

请注意,当StackPanel子元素处于水平排列时,此时子元素的高度默认与StakcPanel的高度保持一致,但是子元素的宽度是与其自身的宽度自适应显示。
深入探究StackPanel控件
可以利用子控件的HorizontalAlignment属性或VerticalAlignment来设置子控件在StackPanel内部的显示位置,比如在垂直排列布局模式下,可以设置HorizontalAlignment属性值,Left表示显示在左则,Right显示在右则,Center则居中显示,Stretch表示拉伸填充显示。
需要注意的是,由于WPF的控件布局都是采用自适应计算每个控件的位置,所以在设置了HorizontalAlignment或VerticalAlignment后,子控件的宽度和高度都会重新计算,主要是根据自身内容的尺寸计算。
<ScrollViewer>
<StackPanel Orientation="Vertical">
<Button Content="WPF中文网1" Margin="20" HorizontalAlignment="Left"/>
<Button Content="WPF中文网2" Margin="20" HorizontalAlignment="Right"/>
<Button Content="WPF中文网3" Margin="20" HorizontalAlignment="Center"/>
<Button Content="WPF中文网4" Margin="20" HorizontalAlignment="Stretch"/>
<Button Content="WPF中文网5" Margin="20" />
<Button Content="WPF中文网6" Margin="20" />
<Button Content="WPF中文网7" Margin="20" />
<Button Content="WPF中文网8" Margin="20" />
<Button Content="WPF中文网9" Margin="20" />
</StackPanel>
</ScrollViewer>

于是,我们可以看到上图中前三行的按钮都是根据自身内容的宽高自适应绘制的。另外,如果StackPanel内部的子控件太多,则需要配合滚动条容器ScrollViewer控件。
和StackPanel类似的控件,还有两个,分别是WrapPanel和DockPanel。下一节,我们将探讨WrapPanel控件。
当前课程源码下载:(注明:本站所有源代码请按标题搜索)
文件名:008-《StackPanel控件(栈式布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff
——重庆教主 2023年8月17日
UniformGrid和Grid有些相似,只不过UniformGrid的每个单元格面积都是相等的,不管是横向的单元格,或是纵向的单元格,它们会平分整个UniformGrid。我们先看看它的结构定义:
public class UniformGrid : Panel
{
public static readonly DependencyProperty FirstColumnProperty;
public static readonly DependencyProperty ColumnsProperty;
public static readonly DependencyProperty RowsProperty;
public UniformGrid();
public int FirstColumn { get; set; }
public int Columns { get; set; }
public int Rows { get; set; }
protected override Size ArrangeOverride(Size arrangeSize);
protected override Size MeasureOverride(Size constraint);
}
UniformGrid控件提供了3个属性,分别是FirstColumn、Columns 、Rows 。FirstColumn表示第一行要空几个单元格,后面两个属性分别用于设置行数和列数。接下来我们以实际的例子来分析这3个属性的用法。
<UniformGrid>
<Button Content="WPF中文网1" Margin="2"/>
<Button Content="WPF中文网2" Margin="2"/>
<Button Content="WPF中文网3" Margin="2"/>
<Button Content="WPF中文网4" Margin="2"/>
</UniformGrid>

这是我们没有UniformGrid的属性的效果,它会根据子元素的数量和UniformGrid自身的尺寸来决定行数和列数。
<UniformGrid FirstColumn="1" Rows="3" Columns="3">
<Button Content="WPF中文网1" Margin="2"/>
<Button Content="WPF中文网2" Margin="2"/>
<Button Content="WPF中文网3" Margin="2"/>
<Button Content="WPF中文网4" Margin="2"/>
</UniformGrid>

我们故意设计了当前UniformGrid为3行3列,同时设置第一行第一个单元格保持空白,于是我们就看到了上图中的效果。
UniformGrid控件使用非常简单方便,通常用于局部的布局。
当前课程源码下载:(注明:本站所有源代码请按标题搜索)
文件名:011-《UniformGrid控件(均分布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff
——重庆教主 2023年8月19日
Grid控件其实是一个窗体的默认控件,我们创建一个WPF应用程序后,其主窗体里面会有一个Grid控件。我们先来看一下此类的结构定义:
public class Grid : Panel, IAddChild
{
public static readonly DependencyProperty ShowGridLinesProperty;
public static readonly DependencyProperty ColumnProperty;
public static readonly DependencyProperty RowProperty;
public static readonly DependencyProperty ColumnSpanProperty;
public static readonly DependencyProperty RowSpanProperty;
public static readonly DependencyProperty IsSharedSizeScopeProperty;
public Grid();
public ColumnDefinitionCollection ColumnDefinitions { get; }
public bool ShowGridLines { get; set; }
public RowDefinitionCollection RowDefinitions { get; }
protected override int VisualChildrenCount { get; }
protected internal override IEnumerator LogicalChildren { get; }
public static int GetColumn(UIElement element);
public static int GetColumnSpan(UIElement element);
public static bool GetIsSharedSizeScope(UIElement element);
public static int GetRow(UIElement element);
public static int GetRowSpan(UIElement element);
public static void SetColumn(UIElement element, int value);
public static void SetColumnSpan(UIElement element, int value);
public static void SetIsSharedSizeScope(UIElement element, bool value);
public static void SetRow(UIElement element, int value);
public static void SetRowSpan(UIElement element, int value);
public bool ShouldSerializeColumnDefinitions();
public bool ShouldSerializeRowDefinitions();
protected override Size ArrangeOverride(Size arrangeSize);
protected override Visual GetVisualChild(int index);
protected override Size MeasureOverride(Size constraint);
protected internal override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved);
}
Grid有两个非常关键的属性ColumnDefinitions和RowDefinitions,分别表示列的数量集合和行的数量集合。ColumnDefinitions集合中的元素类型是ColumnDefinition类,RowDefinitions集合中元素类型是RowDefinition类。默认的Gridr控件没有定义行数和列数,也就是说,Grid默认情况下,行数和列数都等于1,那么它就只有一个单元格。
<Grid >
<Button Content="WPF中文网1" Panel.ZIndex="1" Margin="20 40 60 80" Padding="50" />
<Button Content="WPF中文网2" Panel.ZIndex="0" Margin="20 40 60 80" Padding="50" />
</Grid>
如上述代码所示,此时的Grid因为只有一个单元格,而Grid的Children属性里面有两个Button,势必有一个Button会被遮盖。假如我们希望两个按钮同时显示,应该怎么办呢?这时就有了两个选格,第一,两个Button左右排列显示,第二,两个Button上下排列显示。
一、左右排列
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" Padding="50" />
<Button Grid.Column="1" Content="WPF中文网2" Panel.ZIndex="0" Margin="20" Padding="50" />
</Grid>

代码分析
我们在Grid控件的ColumnDefinitions属性增加了两个ColumnDefinition对象,如果分别设置了两个按钮的Grid.Column附加属性,指示两个Button分别显示在第一列和第二列,从而实现了左右排列,具两个按钮分别占据了50%的区域。这是因为我们并没有指定两个ColumnDefinition对象的宽度。
二、上下排列
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" Padding="50" />
<Button Grid.Row="1" Content="WPF中文网2" Panel.ZIndex="0" Margin="20" Padding="50" />
</Grid>

要实现上下排列,我们只需要在Grid控件的RowDefinitions中增加两行元素即可,即RowDefinition对象。同时,指定每个Button显示在哪一行,例如Grid.Row="0",表示显示在第一行。
三、上下左右排列
现在我们将左右排列和上下排列两种模式合并起来,看看会发生什么状况。
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="0" Grid.Column="1" Content="WPF中文网2" Panel.ZIndex="0" Margin="20" />
<Button Grid.Row="1" Grid.Column="0" Content="WPF中文网3" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="1" Grid.Column="1" Content="WPF中文网4" Panel.ZIndex="0" Margin="20" />
</Grid>

我们创建了4个Button,并分别设置了它们所在的行和列,至此,网格的布局效果跃然纸上。以上就是Grid控件的基本用法。
深入探究Grid控件
在实际开发中,我们可能会遇到更复杂的用法,比如第一行只显示一个button,需要跨列显示,或者第一列只占整个Grid的20%的宽度等等,这就需要了解ColumnDefinition和RowDefinition。另外,我们需要显示Grid的网格线,又该如何实现呢?接下来,我们一一去探寻。
四、跨列排列
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" Grid.ColumnSpan="2"/>
<Button Grid.Row="1" Grid.Column="0" Content="WPF中文网3" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="1" Grid.Column="1" Content="WPF中文网4" Panel.ZIndex="0" Margin="20" />
</Grid>

我们在原有基础上删掉了一个按钮,并将第一个按钮的Grid.ColumnSpan附加属性设置为2,表示从第0列往右跨两列,正好就呈现出图中的效果。您也可以尝试跨行显示,只需要设置按钮的Grid.RowSpan属性。
五、固定列宽
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="0" Grid.Column="1" Content="WPF中文网2" Panel.ZIndex="0" Margin="20" />
<Button Grid.Row="1" Grid.Column="0" Content="WPF中文网3" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="1" Grid.Column="1" Content="WPF中文网4" Panel.ZIndex="0" Margin="20" />
</Grid>

如图所示,我们只需要设置第一行ColumnDefinition的Width属性,让其宽度固定为120像素,那么第二列的宽度等于Grid的宽度减去120像素,其内部的Button宽度也随之自适应。这就是WPF布局自适应的好处。
六、调整行高和列宽
Grid控件的行高和列宽的设置十分丰富,了解它们的用法,有助于设计出更出色的布局。
名称 | 说明 |
绝对设置尺寸 | 使用设备无关单位准确地设置尺寸,就是给一个实际的数字,但通常将此值指定为整数(像素)。如:<ColumnDefinition Width="100"></ColumnDefinition> |
自动设置尺寸 | 值为Auto,实际作用就是取实际控件所需的最小值,每行和每列的尺寸刚好满足需要,这是最有用的尺寸设置方式。如:<ColumnDefinition Width="Auto"></ColumnDefinition> |
按比例设置设置尺寸 | 按比例将空间分割到一组行和列中。这是对所有行和列的标准设置。通常值为*或N*,实际作用就是取尽可能大的值,当某一列或行被定义为*则是尽可能大,当出现多列或行被定义为*则是代表几者之间按比例方设置尺寸。如:<ColumnDefinition Width="*"></ColumnDefinition> |
指定权重,即第2列的宽度是第1列的两倍
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
七、Grid显示网格线
<Grid ShowGridLines="True" Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="0" Grid.Column="1" Content="WPF中文网2" Panel.ZIndex="0" Margin="20" />
<Button Grid.Row="1" Grid.Column="0" Content="WPF中文网3" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="1" Grid.Column="1" Content="WPF中文网4" Panel.ZIndex="0" Margin="20" />
</Grid>

只需要设置Grid的ShowGridLines=True,就可以显示Grid的网格线,但是这种虚线效果并不友好,我们还有曲线救国的方案。
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Gray" BorderThickness="1"/>
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Gray" BorderThickness="0 0 0 1"/>
<Border Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" BorderBrush="Gray" BorderThickness="0 0 1 0"/>
<Button Grid.Row="0" Grid.Column="0" Content="WPF中文网1" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="0" Grid.Column="1" Content="WPF中文网2" Panel.ZIndex="0" Margin="20" />
<Button Grid.Row="1" Grid.Column="0" Content="WPF中文网3" Panel.ZIndex="1" Margin="20" />
<Button Grid.Row="1" Grid.Column="1" Content="WPF中文网4" Panel.ZIndex="0" Margin="20" />
</Grid>

我们在Grid内部增加了3个Border,第一个Border用来显示外边框,第二个Border显示中间的横线,第三个Border显示中间的竖线,这时所用的知识点几乎都是Grid的跨行和跨列属性,另外还有边框颜色刷子BorderBrush和边框厚度BorderThickness。
总结
Grid控件绝对是WPF中所有布局控件中最好用的一个,因为它自适应屏幕的宽度,最关键的一点是,它在呈现时,其ActualWidth实际宽度和ActualHeight实际高度会有一个计算值,我们在业务开发中,有时候要根据父控件的实际宽度和高度来计算子控件的呈现位置和大小。
除了Grid这种网格化的布局,下面我们将介绍另一种布局方式——均分布局
当前课程源码下载:(注明:本站所有源代码请按标题搜索)
文件名:007-《Grid控件(网格布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff
——重庆教主 2023年8月17日