WPF中文网

布局控件概述

软件的界面当然离不开布局,良好的布局是成功的一半。软件的界面是由控件构成的,而控件差不多可分为内容控件和布局控件,比如书架和抽屉是布局控件,而书本、化妆品、人民币都是内容控件。将书本放到书架上,将化妆品、人民币放到抽屉里,家里就会显示干净整洁,要是没有书架和抽屉,这些物品将散落一地,那画面......

在WPF中,布局的方式十分丰富,有按表格布局的Grid和UniformGrid栅格控件,有类似Winform拖放的Canvas控件,有按照垂直或水平排列的StackPanel控件,也有按照东西南北中方位排列的DockPanel控件,还有以瀑布流方式WrapPanel控件,以及按Tab页切换显示的TabControl控件。

这些布局控件都有一个共性,即可以在里面放多个内容控件,毕竟一个书架只能放一本书的话,那也太浪费了。于是它们之间就有了共同的Children属性,微软将它们抽象成Panel基类,并让这个基类继承于FrameworkElement类。

那么,WPF提供了哪些常用的布局控件呢?

控件名称布局方式
Grid网格,根据自定义行和列来设置控件的布局
StackPanel栈式面板,包含的元素在竖直或水平方向排成一条直线
WrapPanel自动折行面板,包含的元素在排满一行后,自动换行
DockPanel泊靠式面板,内部的元素可以选择泊靠方向
UniformGrid网格,UniformGrid就是Grid的简化版,每个单元格的大小相同。
Canvas画布,内部元素根据像素为单位绝对坐标进行定位
Border装饰的控件,此控件用于绘制边框及背景,在Border中只能有一个子控件

这里面除了Border控件,其它控件都继承于Panel基类,下一节,我们将介绍Panel类。

——重庆教主 2023年8月16日

Panel其实是一个抽象类,不可以实例化,WPF所有的布局控件都从Panel继承而来,所以我们在学习布局控件之前,要先了解一下这个类。首先看一下它的定义:

public abstract class Panel : FrameworkElement, IAddChild
{
    public static readonly DependencyProperty BackgroundProperty;
    public static readonly DependencyProperty IsItemsHostProperty;
    public static readonly DependencyProperty ZIndexProperty;

    protected Panel();

    public bool HasLogicalOrientationPublic { get; }
    public Orientation LogicalOrientationPublic { get; }
    public bool IsItemsHost { get; set; }
    public UIElementCollection Children { get; }
    public Brush Background { get; set; }
    protected override int VisualChildrenCount { get; }
    protected internal UIElementCollection InternalChildren { get; }
    protected internal override IEnumerator LogicalChildren { get; }
    protected internal virtual Orientation LogicalOrientation { get; }
    protected internal virtual bool HasLogicalOrientation { get; }

    public static int GetZIndex(UIElement element);
    public static void SetZIndex(UIElement element, int value);
    public bool ShouldSerializeChildren();
    protected virtual UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent);
    protected override Visual GetVisualChild(int index);
    protected virtual void OnIsItemsHostChanged(bool oldIsItemsHost, bool newIsItemsHost);
    protected override void OnRender(DrawingContext dc);
    protected internal override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved);

}

从它的代码定义来看,它继承于FrameworkElement基类和IAddChild接口。所以,所有 Panel 元素都支持 FrameworkElement 定义的基本大小调整和定位属性,包括 Height、Width、HorizontalAlignment、VerticalAlignment、Margin 和 LayoutTransform。

它有一个Background属性,意味着所有的布局控件都可以设置背景颜色。另外,它还有一个Children属性,这是一个集合属性,也就是说,所有的布局控件都可以添加多个子元素。这一点从它继承的IAddChild接口也能得到印证。

namespace System.Windows.Markup
{
    //提供了一种分析允许混合的子元素或文本的元素的方法。
    public interface IAddChild
    {
        //添加子对象。
        void AddChild(object value);
        //将节点的文本内容添加到对象。
        void AddText(string text);
    }

Panel提供了GetZIndex和SetZIndex方法成员,分别表示获取某个元素的ZIndex顺序和设置某个元素的ZIndex顺序。

什么是ZIndex?这是Panel提供的一个附加属性。假如一个单行单列的Grid布局控件中有两个Button,正常情况下,这两个Button都会以撑满Grid的方式呈现在Grid中,那么,到底哪一个Button在上面,哪一个Button在下面呢?就看这两个Button的Panel.ZIndex附加属性的值,值越大越在上面,而值较小的那个Button将被上面的Button遮盖,从而在视觉上,用户只能看到一个Button。

附加属性

附加属性的一个用途是允许子元素存储实际上由父元素定义的属性的唯一值。 此功能的一项应用是让子元素通知父级它们希望如何在用户界面 (UI) 中呈现,这对应用程序布局非常有用。
<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>

在上面的示例中,正常情况下,会显示“WPF中文网2"按钮,但是我们故意叫其Panel.ZIndex=0,从而小于上面那个按钮的属性值,所以显示了“WPF中文网1"按钮。

WPF 提供了一套全面的派生 Panel 实现,可实现许多复杂的布局。这里有一个非常非常需要注意的事项,那就是Panel的Background属性。有时候我们希望在布局控件上实现鼠标点击事件的获取,请记得一定要给Background属性设置一个颜色值,如果不希望有具体的颜色,那就设置成Transparent 。不然,您会踩坑的!因为布局控件的Background属性没有值时,是不能引发鼠标相关事件的

深入探究Panel类

Panel作为布局控件的基类,拥有一个叫Children的属性,这个属性的类型是UIElementCollection。我们来看一下它的结构:

public class UIElementCollection : IList, ICollection, IEnumerable
{
    public UIElementCollection(UIElement visualParent, FrameworkElement logicalParent);

    public virtual UIElement this[int index] { get; set; }

    public virtual int Capacity { get; set; }
    public virtual object SyncRoot { get; }
    public virtual bool IsSynchronized { get; }
    public virtual int Count { get; }

    public virtual int Add(UIElement element);
    public virtual void Clear();
    public virtual bool Contains(UIElement element);
    public virtual void CopyTo(UIElement[] array, int index);
    public virtual void CopyTo(Array array, int index);
    public virtual IEnumerator GetEnumerator();
    public virtual int IndexOf(UIElement element);
    public virtual void Insert(int index, UIElement element);
    public virtual void Remove(UIElement element);
    public virtual void RemoveAt(int index);
    public virtual void RemoveRange(int index, int count);
    protected void ClearLogicalParent(UIElement element);
    protected void SetLogicalParent(UIElement element);

}

从它所定义的方法来看,我们会看到一些添加或移除某个元素的方法成员,例如Add,Insert,Remove,Contains等等,而这些方法的参数都有一个叫UIElement的形参,说明什么问题?只要继承于UIElement的类(或控件),都可以添加到Panel或Panel子类的Children中,从而在前端呈现出来。

WPF提供了六个用于UI布局的Panel子类,分别是:Grid、StackPanel、WrapPanel、DockPanel、 VirtualizingStackPanel和 Canvas。 这些面板元素易于使用、功能齐全并且可扩展,足以适用于大多数应用程序。

一个Panel 的呈现就是测量和排列子控件,然后在屏幕上绘制它们。所以在布局的过程中会经过一系列的计算,那么子控件越多,执行的计算次数就越多,则性能就会变差。如果不需要进行复杂的布局,则尽量少用复杂布局控件(如 Grid和自定义复杂的Panel);如果能简单布局实现就尽量使用构造相对简单的布局(如 Canvas、UniformGrid等),这种布局可带来更好的性能。 如果有可能,我们应尽量避免调用 UpdateLayout方法。

布局系统为Panel中的每个子控件完成两个处理过程:测量处理过程(Measure)和排列处理过程(Arrange)。每个子 Panel 均提供自己的 MeasureOverride 和 ArrangeOverride 方法,以实现自己特定的布局行为。

每个派生 Panel 元素都以不同方式处理大小调整约束。 了解 Panel 如何处理水平或垂直方向上的约束可以使布局更容易预测。

控件名称x维度y维度
Grid约束约束,Auto 应用于行和列的情形除外
StackPanel(垂直)约束按内容约束
StackPanel(水平)按内容约束约束
DockPanel约束约束
WrapPanel按内容约束按内容约束
Canvas按内容约束按内容约束

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:006-《Panel类》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月16日

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日

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日

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日

WrapPanel控件表示将其子控件从左到右的顺序排列,如果第一行显示不了,则自动换至第二行,继续显示剩余的子控件。我们来看看它的结构定义:

public class WrapPanel : Panel
{
    public static readonly DependencyProperty ItemWidthProperty;
    public static readonly DependencyProperty ItemHeightProperty;
    public static readonly DependencyProperty OrientationProperty;

    public WrapPanel();

    public double ItemWidth { get; set; }
    public double ItemHeight { get; set; }
    public Orientation Orientation { get; set; }

    protected override Size ArrangeOverride(Size finalSize);
    protected override Size MeasureOverride(Size constraint);

}

这个控件比较简单,只提供了3个属性,分别是Orientation代表子控件的排列方向,ItemWidth代表子控件的(最大)宽度,ItemHeight代表子控件的(最大)高度。默认的排列方向是水平方向。

一、水平排列

<WrapPanel Orientation="Horizontal">
    <Button Content="WPF中文网1" Margin="5" HorizontalAlignment="Left"/>
    <Button Content="WPF中文网2" Margin="5" HorizontalAlignment="Right"/>
    <Button Content="WPF中文网3" Margin="5" HorizontalAlignment="Center"/>
    <Button Content="WPF中文网4" Margin="5" HorizontalAlignment="Stretch"/>
    <Button Content="WPF中文网5" Margin="5" />
    <Button Content="WPF中文网6" Margin="5" />
    <Button Content="WPF中文网7" Margin="5" />
    <Button Content="WPF中文网8" Margin="5" />
    <Button Content="WPF中文网9" Margin="5" />
    <Button Content="WPF中文网10" Margin="5" />
</WrapPanel>

由上图所示,我们在WrapPanel的Children属性中放了10个Button,分成了两行显示,请注意一个细节,WrapPanel的子元素的高度和宽度都是根据子元素自身内容的尺寸呈现。另外,当WrapPanel处于水平排列时,子元素的HorizontalAlignment是不起作用的。

二、垂直排列

<WrapPanel Orientation="Vertical">
    <Button Content="WPF中文网1" Margin="5" HorizontalAlignment="Left"/>
    <Button Content="WPF中文网2" Margin="5" HorizontalAlignment="Right"/>
    <Button Content="WPF中文网3" Margin="5" HorizontalAlignment="Center"/>
    <Button Content="WPF中文网4" Margin="5" HorizontalAlignment="Stretch"/>
    <Button Content="WPF中文网5" Margin="5" />
    <Button Content="WPF中文网6" Margin="5" />
    <Button Content="WPF中文网7" Margin="5" />
    <Button Content="WPF中文网8" Margin="5" />
    <Button Content="WPF中文网9" Margin="5" />
    <Button Content="WPF中文网10" Margin="5" />
    <Button Content="WPF中文网12" Margin="5" />
    <Button Content="WPF中文网13" Margin="5" />
    <Button Content="WPF中文网14" Margin="5" />
    <Button Content="WPF中文网15" Margin="5" />
    <Button Content="WPF中文网16" Margin="5" />
    <Button Content="WPF中文网17" Margin="5" />
    <Button Content="WPF中文网18" Margin="5" />
    <Button Content="WPF中文网19" Margin="5" />
    <Button Content="WPF中文网20" Margin="5" />

</WrapPanel>

这里我们放了20个button在WrapPanel控件中,并设置Orientation属性为Vertical(垂直排列),此时,请观察前面3个按钮的HorizontalAlignment状态,可以很清晰的看到,第一个按钮居左显示,第二个按钮居右显示,第三个按钮居中显示,说明在Vertical垂直排列下,子元素的水平状态才会生效,反之亦然。

三、指定子元素宽高

<WrapPanel Orientation="Horizontal" ItemWidth="80" ItemHeight="80">
    <Button Content="WPF中文网1" Margin="5" HorizontalAlignment="Left"/>
    <Button Content="WPF中文网2" Margin="5" HorizontalAlignment="Right"/>
    <Button Content="WPF中文网3" Margin="5" HorizontalAlignment="Center"/>
    <Button Content="WPF中文网4" Margin="5" HorizontalAlignment="Stretch"/>
    <Button Content="WPF中文网5" Margin="5" />
    <Button Content="WPF中文网6" Margin="5" />
    <Button Content="WPF中文网7" Margin="5" />
    <Button Content="WPF中文网8" Margin="5" />
    <Button Content="WPF中文网9" Margin="5" />
    <Button Content="WPF中文网10" Margin="5" />
</WrapPanel>

由此可以,我们也可以指定子元素的宽度和高度,以便统一观察。下一节,我们将探讨与WrapPanel非常相似的布局控件DockPanel。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:009-《WrapPanel控件(栈式布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月18日

官方解释,定义一个区域,从中可以按相对位置水平或垂直排列各个子元素。我们还是先来看一下它的结构定义:

public class DockPanel : Panel
{
    public static readonly DependencyProperty LastChildFillProperty;
    public static readonly DependencyProperty DockProperty;

    public DockPanel();

    public bool LastChildFill { get; set; }

    public static Dock GetDock(UIElement element);
    public static void SetDock(UIElement element, Dock dock);
    protected override Size ArrangeOverride(Size arrangeSize);
    protected override Size MeasureOverride(Size constraint);

}

DockPanel提供了一个LastChildFill 属性,用来指示最后一个子元素是否填满剩余的空间。其次,它还提供了一个枚举依赖属性,叫Dock。这个属性是附加到子元素身上的,用来指示子元素在DockPanel显示停靠方位,其值分为Left,Right,Top,Bottom。

DockPanel因为继承了FrameworkElement基类,所以您还可以使用FrameworkElement基类的HorizontalAlignment(水平对齐)和VerticalAlignment(垂直对齐)两个属性,用来设置子元素的排列方式。接下来我们将演示它最基本的用法。

一、经典布局

<DockPanel>
    <Button DockPanel.Dock="Left" Content="WPF中文网1" Margin="5" />
    <Button DockPanel.Dock="Top" Content="WPF中文网2" Margin="5" />
    <Button DockPanel.Dock="Right" Content="WPF中文网3" Margin="5" />
    <Button DockPanel.Dock="Bottom" Content="WPF中文网4" Margin="5" />
    <Button  Content="WPF中文网5" Margin="5" />
</DockPanel>

这是DockPanel最经典的布局方式,上下左右都停靠一个控件,中间剩余的空间,全部由最后一个控件填满。

二、水平布局

<DockPanel LastChildFill="False" HorizontalAlignment="Center">
    <Button Content="WPF中文网1" Margin="5" />
    <Button Content="WPF中文网2" Margin="5" />
    <Button Content="WPF中文网3" Margin="5" />
    <Button Content="WPF中文网4" Margin="5" />
    <Button Content="WPF中文网5" Margin="5" />
</DockPanel>

我们只需要设置LastChildFill为False,并设置HorizontalAlignment属性,并再指定子控件的停靠方向,就可以达到上图的效果。

<DockPanel LastChildFill="False" VerticalAlignment="Top">
    <Button Content="WPF中文网1" Margin="5"/>
    <Button Content="WPF中文网2" Margin="5"/>
    <Button Content="WPF中文网3" Margin="5"/>
    <Button Content="WPF中文网4" Margin="5"/>
    <Button Content="WPF中文网5" Margin="5"/>
</DockPanel>

你可以通过摸索HorizontalAlignment(水平对齐)和VerticalAlignment(垂直对齐)两个属性,还可以达到更多意想不到的效果哦。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:010-《DockPanel控件(停靠布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月18日

VirtualizingStackPanel 类(虚拟化元素)和StackPanel 类在用法上几乎差不多。其作用是在水平或垂直的一行中排列并显示内容。它继承于一个叫VirtualizingPanel的抽象类,而这个VirtualizingPanel抽象类继承于Panel布局基类。

public class VirtualizingStackPanel : VirtualizingPanel, IScrollInfo, IStackMeasure
{
    public static readonly DependencyProperty IsVirtualizingProperty;
    public static readonly DependencyProperty VirtualizationModeProperty;
    public static readonly DependencyProperty OrientationProperty;
    public static readonly RoutedEvent CleanUpVirtualizedItemEvent;

    public VirtualizingStackPanel();

    public double VerticalOffset { get; }
    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 ScrollViewer ScrollOwner { get; set; }
    protected override bool CanHierarchicallyScrollAndVirtualizeCore { get; }
    protected internal override Orientation LogicalOrientation { get; }
    protected internal override bool HasLogicalOrientation { get; }

    public static void AddCleanUpVirtualizedItemHandler(DependencyObject element, CleanUpVirtualizedItemEventHandler handler);
    public static void RemoveCleanUpVirtualizedItemHandler(DependencyObject element, CleanUpVirtualizedItemEventHandler handler);
    public virtual void LineDown();
    public virtual void LineLeft();
    public virtual void LineRight();
    public virtual void LineUp();
    public Rect MakeVisible(Visual visual, Rect rectangle);
    public virtual void MouseWheelDown();
    public virtual void MouseWheelLeft();
    public virtual void MouseWheelRight();
    public virtual void MouseWheelUp();
    public virtual void PageDown();
    public virtual void PageLeft();
    public virtual void PageRight();
    public virtual void PageUp();
    public void SetHorizontalOffset(double offset);
    public void SetVerticalOffset(double offset);
    protected override Size ArrangeOverride(Size arrangeSize);
    protected override double GetItemOffsetCore(UIElement child);
    protected override Size MeasureOverride(Size constraint);
    protected virtual void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs e);
    protected override void OnClearChildren();
    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args);
    protected virtual void OnViewportOffsetChanged(Vector oldViewportOffset, Vector newViewportOffset);
    protected virtual void OnViewportSizeChanged(Size oldViewportSize, Size newViewportSize);
    protected override bool ShouldItemsChangeAffectLayoutCore(bool areItemChangesLocal, ItemsChangedEventArgs args);
    protected internal override void BringIndexIntoView(int index);

}

VirtualizingStackPanel 类有什么作用?

比如在ListBox集合控件中需要显示500条数据,那整个屏幕只能显示20条,剩余的480条数据在ListBox控件要不要一次性绘制出来?其实就算绘制出来,用户的屏幕也看不见,只能是拖动滚动条才能看见后面的数据。既然屏幕只能显示20条数据,何不只绘制20条数据的UI子元素,剩下的480条数据的子元素在拖动滚动条时才绘制,这将大大减少计算机的性能消耗,提高UI界面的呈现速度,提高软件的流畅性。

所以,VirtualizingStackPanel 类的作用是开启虚拟化技术,延迟那些看不见的子元素的绘制与渲染。

要开启这项技术,只需要设置Listbox集合控件的附加属性VirtualizingStackPanel.IsVirtualizing="True"即可。因为ListBox的ItemsPanel(元素布局模板)默认采用了VirtualizingStackPanel控件布局。

——重庆教主 2023年8月20

Canvas控件允许我们像Winform一样拖拽子控件进行布局,而子控件的位置相对于Canvas来说是绝对的,所以我将它称为绝对布局。我们来看看它的结构定义:

public class Canvas : Panel
{
    public static readonly DependencyProperty LeftProperty;
    public static readonly DependencyProperty TopProperty;
    public static readonly DependencyProperty RightProperty;
    public static readonly DependencyProperty BottomProperty;

    public Canvas();

    public static double GetBottom(UIElement element);
    public static double GetLeft(UIElement element);
    public static double GetRight(UIElement element);
    public static double GetTop(UIElement element);
    public static void SetBottom(UIElement element, double length);
    public static void SetLeft(UIElement element, double length);
    public static void SetRight(UIElement element, double length);
    public static void SetTop(UIElement element, double length);
    protected override Size ArrangeOverride(Size arrangeSize);
    protected override Geometry GetLayoutClip(Size layoutSlotSize);
    protected override Size MeasureOverride(Size constraint);

}

观察它的结构,我们可以看到它提供了4个依赖属性,分别是LeftProperty,RightProperty,TopProperty和BottomProperty。其实是将这4个属性附加到子元素身上,以此来设置子元素距离Canvas上下左右的像素位置。

<Canvas>
    <Button  Content="WPF中文网1" Margin="5" />
    <Button  Content="WPF中文网2" Margin="5" />
    <Button  Content="WPF中文网3" Margin="5" />
    <Button  Content="WPF中文网4" Margin="5" />
    <Button  Content="WPF中文网5" Margin="5" />
</Canvas>

上面的示例并没有指定button控件在Canvas控件中的上下左右停靠位置,所以这5个button默认会显示在Canvas的左上角,且只能显示最后一个,前面4个会被遮盖。我们来看看下面的例子。

<Canvas>
    <Button  Content="WPF中文网1" Margin="5" Canvas.Left="50"/>
    <Button  Content="WPF中文网2" Margin="5" Canvas.Top="50"/>
    <Button  Content="WPF中文网3" Margin="5" Canvas.Right="50"/>
    <Button  Content="WPF中文网4" Margin="5" Canvas.Bottom="50"/>
    <Button  Content="WPF中文网5" Canvas.Left="200" Canvas.Top="150" />
</Canvas>

上面的源代码中,我们从上到下,分别来分析一下5个button。

第一个button,设置了Canvas.Left="50",它将保持距离Canvas左边50像素。

第二个button,设置了Canvas.Top="50",它将保持距离Canvas顶部50像素。

第三个button,设置了Canvas.Right="50",它将保持距离Canvas右侧50像素。

第四个button,设置了Canvas.Bottom="50",它将保持距离Canvas底部50像素。

第五个button,设置了Canvas.Left="200" Canvas.Top="150",也就是同时距离Canvas左边200像素,顶部150像素。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:012-《Canvas控件(绝对布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月22日

严格来说,Border并不是一个布局控件,因为它并不是Panel的子类,而是Decorator装饰器的子类,而Decorator继承于FrameworkElement。要了解Border的用法,我们要先看看它的父类Decorator。

public class Decorator : FrameworkElement, IAddChild
{
    public Decorator();

    public virtual UIElement Child { get; set; }
    protected override int VisualChildrenCount { get; }
    protected internal override IEnumerator LogicalChildren { get; }

    protected override Size ArrangeOverride(Size arrangeSize);
    protected override Visual GetVisualChild(int index);
    protected override Size MeasureOverride(Size constraint);

}

Decorator 装饰器只有一个Child 属性,说明Decorator只能容纳一个子元素(UIElement),也就是Border只能容纳一个子元素。那我们再看看Border的结构定义:

public class Border : Decorator
{
    public static readonly DependencyProperty BorderThicknessProperty;
    public static readonly DependencyProperty PaddingProperty;
    public static readonly DependencyProperty CornerRadiusProperty;
    public static readonly DependencyProperty BorderBrushProperty;
    public static readonly DependencyProperty BackgroundProperty;

    public Border();

    public Thickness BorderThickness { get; set; }
    public Thickness Padding { get; set; }
    public CornerRadius CornerRadius { get; set; }
    public Brush BorderBrush { get; set; }
    public Brush Background { get; set; }

    protected override Size ArrangeOverride(Size finalSize);
    protected override Size MeasureOverride(Size constraint);
    protected override void OnRender(DrawingContext dc);

}

我们直接以表格的形式给出Border的相关属性。

属性说明
BorderThickness 设置Border边框的厚度(像素宽度)
Padding 设置子元素相对于Border边框的距离
CornerRadius 设置Border的圆角
BorderBrush 设置Border边框的颜色画刷
Background设置Border的背景颜色画刷

正是因为Border有这么多实用的属性, 所以, 我们通常在布局界面时,Border(装饰器)控件是首选。接下来,我们以一个例子来说明Border有多么好用。

<WrapPanel Margin="10">
    <Border Height="35" Margin="10" Padding="5" BorderThickness="1" BorderBrush="Gray">
        <TextBlock  Text="矩形 - Border控件" Margin="5" />
   </Border>
    <Border Height="35" Margin="10" Padding="5" BorderThickness="1" BorderBrush="Gray" CornerRadius="20">
        <TextBlock  Text="椭圆 - Border控件" Margin="5" />
    </Border>
    <Border Width="150" Height="150" Margin="10" Padding="5" BorderThickness="1" 
            Background="Red" BorderBrush="Gray" CornerRadius="75">
        <TextBlock  Text="圆形Border控件" Margin="5" HorizontalAlignment="Center" 
                    FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Foreground="White"/>
    </Border>
</WrapPanel>

我们分别写了3个Border,第一个Border被设计成矩形,第二个Border增加了圆角属性,第三个Border通过CornerRadius属性,将值设置为宽度或高度的一半,就形成了一个正圆。

将来,我们再配合WPF的模板、样式、触发器会让Border的用法更上一层楼。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:013-《Border控件(边框布局)》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月22日

copyright @重庆教主 WPF中文网 联系站长:(QQ)23611316 (微信)movieclip (QQ群).NET小白课堂:864486030 | 本文由WPF中文网原创发布,谢绝转载 渝ICP备2023009518号-1