• 《WPF编程宝典(第2版)》第3章 布局


    第3章 布局

    在任意用户界面设计中,有一半的工作量是以富有吸引力、灵活实用的方式组织内容。但真正的挑战是确保界面布局能够恰到好处地适应不同的窗口尺寸。

    WPF用不同的容器(container)安排布局。每个容器有各自的布局逻辑——有些容器以堆栈方式布置元素,另一些容器在网格中不可见的单元格中排列元素,等等。在WPF中非常抵制基于坐标的布局,而是注重创建更灵活的布局,使布局能够适应内容的变化、不同的语言以及各种窗口尺寸。迁移到WPF的许多开发人员会觉得新布局系统令自己倍感惊奇——这也是开发人员面临的第一个真正挑战。

    本章将介绍WPF布局模型的工作原理,并且将开始使用基本的布局容器。为了学习WPF布局的基础知识,本章将介绍几个通用的布局示例——从基本的对话框乃至可改变尺寸的拆分窗口。

    3.1 理解WPF中的布局

    在Windows开发人员设计用户界面的方式上,WPF布局模型是一个重大改进。在WPF问世之前,Windows开发人员使用刻板的基于坐标的布局将控件放到正确的位置。在WPF中这种方式虽然可行,但已经极少使用。大多数应用程序将使用类似于Web的流(flow)布局;在使用流布局模型时,控件可以扩大,并将其他控件挤到其他位置,开发人员能创建与显示分辨率和窗口大小无关的、在不同的显示器上正确缩放的用户界面;当窗口内容发生变化时,界面可调整自身,并且可以自如地处理语言的切换。要利用该系统的优势,首先需要进一步理解WPF布局模型的基本概念和假设。

    3.1.1 WPF布局原则

    WPF窗口只能包含单个元素。为在WPF窗口中放置多个元素并创建更贴近实用的用户界面,需要在窗口上放置一个容器,然后在这个容器中添加其他元素。

    注意:

    造成这一限制的原因是Window类继承自ContentControl类,在第6章中将进一步分析ContentControl类。

    在WPF中,布局由您使用的容器来确定。尽管有多个容器可供选择,但“理想的”WPF窗口需要遵循以下几条重要原则:

    • 不应显式设定元素(如控件)的尺寸。元素应当可以改变尺寸以适合它们的内容。例如,当添加更多的文本时按钮应当能够扩展。可通过设置最大和最小尺寸来限制可以接受的尺寸范围。
    • 不应使用屏幕坐标指定元素的位置。元素应当由它们的容器根据它们的尺寸、顺序以及(可选的)其他特定于具体布局容器的信息进行排列。如果需要在元素之间添加空白空间,可使用Margin属性。

    提示:

    以硬编码方式设定尺寸和位置是极其不当的处理方式,因为这会限制本地化界面的能力,并且会使界面更难处理动态内容。

    • 布局容器的子元素“共享”可用的空间。如果空间允许,布局容器会根据每个元素的内容尽可能为元素设置更合理的尺寸。它们还会向一个或多个子元素分配多余的空间。
    • 可嵌套的布局容器。典型的用户界面使用Grid面板作为开始,Grid面板是WPF中功能最强大的容器,Grid面板可包含其他布局容器,包含的这些容器以更小的分组排列元素,比如带有标题的文本框、列表框中的项、工具栏上的图标以及一列按钮等。

    尽管对于这几条原则而言也有一些例外,但它们反映了WPF的总体设计目标。换句话说,如果创建WPF应用程序时遵循了这些原则,将会创建出更好的、更灵活地用户界面。如果不遵循这些原则,最终将得到不是很适合WPF的并且难以维护的用户界面。

    3.1.2 布局过程

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

    当然,元素为必总能得到最合适的尺寸——有时容器没有足够大的空间以适应所含的元素。在这种情况下,容器为了适应可视化区域的尺寸,就必须剪裁不能满足要求的元素。在后面可以看到,通常可通过设置最小窗口尺寸来避免这种情况。

    注意:

    布局容器不能提供任何滚动支持。相反,滚动是由特定的内容控件——ScrollViewer——提供的,ScrollViewer控件几乎可用于任何地方。在第6章中将学习ScrollViewer控件的相关内容。

    3.1.3 布局容器

    所有WPF布局容器都是派生自System.Windows.Controls.Panel抽象类的面板(见图3-1)。Panel类添加了少量成员,包括三个公有属性,表3-1列出了这三个公有属性的详情。

    注意:

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

    就Panel基类本身而言没有什么特别的,但它是其他更多特殊类的起点。WPF提供了大量可用于安排布局的继承自Panel的类,表3-2中列出了其中几个最基本的类。与所有WPF控件和大多数可视化元素一样,这些类位于System.Windows.Controls名称空间中。

    除这些核心容器外,还有几个更专业的面板,在各种控件中都可能遇到它们。这些容器包括专门用于包含特定控件子元素的面板——如TabPanel面板(在TabPanel面板中包含多个选项卡)、ToolbarPanel面板(工具栏中的多个按钮)以及ToolbarOverflowPanel面板(Toolbar控件的溢出菜单中的多个命令)。还有VirtualizingStackPanel面板,数据绑定列表控件使用该面板以大幅降低开销;还有InkCanvas控件,该控件和Canvas控件类似,但该控件支持处理平板电脑(TablePC)上的手写笔(stylus)输入(例如,根据选择的模式,InkCanvas控件支持使用指针绘制范围,以选择屏幕上的元素。也可通过普通计算机和鼠标使用InkCanvas控件,尽管这有点违反直觉)。本章将介绍InkCanvas,第19章将详细介绍VirtualizingStackPanel,在本书其他地方谈到相关控件时,将介绍其他专门的面板。

    3.2 使用StackPanel面板进行简单布局

    StackPanel面板是最简单地布局容器之一。该面板简单地在单行或单列中以堆栈形式布置其子元素。

    例如,分析下面的窗口,该窗口包含4个按钮:

    <Window
        x:Class="StackPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:StackPanel"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Layout" Width="354" Height="223"
        mc:Ignorable="d">
        <StackPanel>
            <Label>A Button Stack</Label>
            <Button>Button 1</Button>
            <Button>Button 2</Button>
            <Button>Button 3</Button>
            <Button>Button 4</Button>
        </StackPanel>
    </Window>
    
    

    图3-2显示了最终得到的窗口。

    在Visual Studio中添加布局容器

    在Visual Studio中使用设计器创建这个示例要比较容易。首先删除Grid根元素(如果有的话)。然后将一个StackPanel面板拖动到窗口上。接下来将其他元素以所希望的自上而下的顺序(标签和4个按钮)拖放到窗口上。如果想重新排列StackPanel面板中的元素,可以简单地将它们拖动到新的位置。

    虽然本书不会占用大量的篇幅来讨论Visual Studio的设计时支持特性,但实际上,自从推出首个WPF版本以来,Visual Studio已经做了很大的改进。例如,Visual Studio不再为添加到设计器中的每个新控件指定名称;而且除非您手动调整大小,Visual Studio不再添加硬编码的Width值和Height值。

    默认情况下,StackPanel面板按自上而下的顺序排列元素,使每个元素的高度适合它的内容。在这个示例中,这意味着标签和按钮的大小刚好足够适应它们内部包含的文本。所有元素都被拉伸到StackPanel面板的整个宽度,这也是窗口的宽度。如果加宽窗口,StackPanel面板也会边框,并且按钮也会拉伸自身以适应变化。

    通过设置Orientation属性,StackPanel面板也可用于水平排列元素:

    <StackPanel Orientation="Horizontal">
    

    现在,元素指定它们的最小宽度(足以适合它们所包含的文本)并拉伸至容器面板的整个高度。根据窗口的当前大小,这可能导致一些元素不适应,如图3-3所示。

    显然,这并未提供实际应用程序所需的灵活性。幸运的是,可使用布局属性对StackPanel面板和其他布局容器的工作方式进行精细调整,如稍后所述。

    3.2.1 布局属性

    尽管布局由容器决定,但子元素仍有一定的决定权。实际上,布局面板支持一小组布局属性,以便与子元素结合使用,在表3-3中列出了这些布局属性。

    所有这些属性都从FrameworkElement基类继承而来,所以在WPF窗口中可使用的所有图形小组件都支持这些属性。

    注意:

    您在第2章中已学习过,不同的布局容器可以为它们的子元素提供附加属性。例如,Grid对象的所有子元素可以获得Row和Column属性,以便选择容纳它们的单元格。通过附加属性可为特定的布局容器设置其特有的信息。然而,在表3-3中列出的布局属性是可以应用于许多布局面板的通用属性。因此,这些属性被定义为FrameworkElement基类的一部分。

    这个属性列表就像它所没有包含的属性一样值得注意。如果查找熟悉的与位置相关的属性,例如Top属性、Right属性以及Location属性,是不会找到它们的。这是因为大多数布局容器(Canvas控件除外)都使用自动布局,并未提供显式定位元素的能力。

    3.2.2 对齐方式

    为理解这些属性的工作原理,可进一步分析图3-2中显示的简单StackPanel面板。在示例中——有一个垂直方向的StackPanel面板——VerticalAlignment属性不起作用,因为所有元素的高度都自动地调整为刚好满足各自需求。但HorizontalAlignment属性非常重要,它决定了各个元素在行的什么位置。

    通常,对于Label控件,HorizontalAlignment属性的值默认为Left;对于Button控件,HorizontalAlignment属性的值默认为Stretch。这也是为什么每个按钮的宽度被调整为整列的宽度的原因所在。但可以改变这些细节:

    <StackPanel>
        <Label HorizontalAlignment="Center">A Button Stack</Label>
        <Button HorizontalAlignment="Left">Button 1</Button>
        <Button HorizontalAlignment="Right">Button 2</Button>
        <Button>Button 3</Button>
        <Button>Button 4</Button>
    </StackPanel>
    

    图3-4显示了最终结果。现在前面两个按钮的尺寸是它们应当具有的最小尺寸,并进行了对齐,而底部两个按钮被拉伸至整个StackPanel面板的宽度。如果改变窗口的尺寸,就会发现标签保持在中间位置,而前两个按钮分别被粘贴到两边。

    注意:

    StackPanel面板也有自己的HorizontalAlignment和VerticalAlignment属性。这两个属性默认都被设置为Stretch,所以StackPanel面板完全充满它的容器。在这个示例中,这意味着StackPanel面板充满整个窗口。如果使用不同设置,StackPanel面板的尺寸将足够宽以容纳最宽的控件。

    3.2.3 边距

    3.2.4 最小尺寸、最大尺寸以及显式地设置尺寸

    3.2.5 Border控件

    Border控件不是布局面板,而是非常便于使用的元素,经常与面板一起使用。所以,在继续介绍其他布局面板之前,现在先介绍一下Border控件是有意义的。

    Border类非常简单。它只能包含一段嵌套内容(通常是布局面板),并为其添加背景或在其周围添加边框。为了深入理解Border控件,只需要掌握表3-4中列出的属性就可以了。

    下面是一个具有轻微圆角效果的简单边框,该边框位于一组按钮的周围,这组按钮包含在一个StackPanel面板中:

    <Border Margin="5" Padding="5" VerticalAlignment="Top" Background="LightYellow" BorderBrush="SteelBlue" BorderThickness="3,5,3,5" CornerRadius="3">
        <StackPanel>
            <Button Margin="3">One</Button>
            <Button Margin="3">Two</Button>
            <Button Margin="3">Three</Button>
            <Button Margin="3">Four</Button>
        </StackPanel>
    </Border>
    

    图3-8显示了该例的结果。

    第6章将介绍有关画刷和颜色的详情,它们可用于设置BorderBrush和Background属性。

    注意:

    从技术角度看,Border是装饰元素(decorator),装饰元素是特定类型的元素,通常用于在对象周围添加某些种类的图形装饰。所有装饰元素都继承自System.Windows.Controls.Decorator类。大多数装饰元素设计用于特定控件。例如,Button控件使用ButtonChrome装饰元素,以获取其特有的圆角和阴影背景效果;而ListBox控件使用ListBoxChrome装饰元素。还有两个更通用的装饰元素,当构造用户界面时它们非常有用:在此讨论的Border元素以及将在第12章中研究的Viewbox元素。

    3.3 WrapPanel和DockPanel面板

    显然,只使用StackPanel面板还不能帮助您创建出实用的用户界面。要设计出最终使用的用户界面,StackPanel面板还需要与其他更强大的布局容器协作。只有这样才能组装成完整的窗口。

    最复杂的布局容器是Grid面板,稍后将分析该面板。在介绍Grid面板之前,有必要首先看一下WrapPanel和DockPanel面板,它们是WPF提供的两个更简单地布局容器。这两个布局容器通过不同的布局行为对StackPanel面板进行补充。

    3.3.1 WrapPanel面板

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

    提示:

    与StackPanel面板类似,WrapPanel面板实际上主要用来控制用户界面中一小部分的布局细节,并非用于控制整个窗口布局。例如,可能使用WrapPanel面板以类似工具栏控件的方式将所有按钮保持在一起。

    下面的示例中定义了一系列具有不同对齐方式的按钮,并将这些按钮放到一个WrapPanel面板中:

    <WrapPanel Margin="3">
        <Button VerticalAlignment="Top">Top Button</Button>
        <Button MinHeight="60">Tall Button 2</Button>
        <Button VerticalAlignment="Bottom">Bottom</Button>
        <Button>Stretch</Button>
        <Button VerticalAlignment="Center">Centered Button</Button>
    </WrapPanel>
    

    图3-9显示了如何对这些按钮进行换行以适应WrapPanel面板的当前尺寸(WrapPanel面板的当前尺寸是由包含它的窗口的尺寸决定的)。正如这个示例所演示的,WrapPanel面板水平的创建了一系列假想的行,每一行的高度都被设置为所包含元素中最高元素的高度。其他控件可能被拉伸以适应这一高度,或根据VerticalAlignment(垂直对齐)属性的设置进行对齐。在图3-9的左图中,所有按钮都在位于较高的行中,并被拉伸或对齐以适应该行的高度。在右图中,有几个按钮被挤到第二行中。因为第二行没有包含特别高的按钮,所以第二行的高度保持为最小按钮的高度。因此,在该行中不必关心各按钮的VerticalAlignment属性的设置。

    注意:

    WrapPanel面板是唯一一个不能通过灵活使用Grid面板代替的面板。

    3.3.2 DockPanel面板

    DockPanel面板是更有趣的布局选项。它沿着一条外边缘来拉伸所包含的控件。理解该面板最简便的方法是,考虑一下位于许多Windows应用程序窗口顶部的工具栏。这些工具栏停靠到窗口顶部。与StackPanel面板类似,被停靠的元素选择它们布局的一个方面。例如,如果将一个按钮停靠在DockPanel面板的顶部,该按钮会被拉伸至DockPanel面板的整个宽度,但根据内容和MinHeight属性为其设置所需的高度。而如果将一个按钮停靠到容器左边,该按钮的高度将被拉伸以适应容器的高度,而其宽度可以根据需要自由增加。

    这里很明显的问题是:子元素如何选择停靠的边?答案是通过Dock附加属性,可将该属性设置为Left、Right、Top或Bottom。放在DockPanel面板中的每个元素都会自动捕获该属性。

    下面的示例在DockPanel面板的每条边上都停靠一个按钮:

    <DockPanel LastChildFill="True">
        <Button DockPanel.Dock="Top">Top Button</Button>
        <Button DockPanel.Dock="Bottom">Bottom Button</Button>
        <Button DockPanel.Dock="Left">Left Button</Button>
        <Button DockPanel.Dock="Right">Right Button</Button>
        <Button>Remaining Space</Button>
    </DockPanel>
    

    该例还将DockPanel面板的LastChildFill属性设置为true,该设置告诉DockPanel面板使最后一个元素占满剩余空间。图3-10显示了结果。

    显然,当停靠控件时,停靠顺序很重要。在这个示例中,顶部和底部按钮充满了DockPanel使最后一个元素占满剩余空间。图3-10显示了结果。

    显然,当停靠控件时,停靠顺序很重要。在这个示例中,顶部和底部按钮充满了DockPanel面板的整个边缘,这是因为这两个按钮首先被停靠。接着停靠左边和右边的按钮时,这两个按钮将位于顶部按钮和底部按钮之间。如果改变这一顺序,那么左边和右边的按钮将充满整个面板的边缘,而顶部和底部的按钮则变窄一些,因为它们将在左边和右边的两个按钮之间进行停靠。

    可将多个元素停靠到同一边缘。这种情况下,元素按标记中声明的顺序停靠到边缘。而且,如果不喜欢空间分割或拉伸行为,可修改Margin属性、HorizontalAlignment(水平排列)属性以及VerticalAlignment(垂直排列)属性,就像使用StackPanel面板进行布局时所介绍的那样。下面是前面演示的程序的修改版本:

    <DockPanel LastChildFill="True">
        <Button DockPanel.Dock="Top">A Stretched Top Button</Button>
        <Button HorizontalAlignment="Center" DockPanel.Dock="Top">A Centered Top Button</Button>
        <Button HorizontalAlignment="Left" DockPanel.Dock="Top">A Left-Aligned Top Button</Button>
        <Button DockPanel.Dock="Bottom">Bottom Button</Button>
        <Button DockPanel.Dock="Left">Left Button</Button>
        <Button DockPanel.Dock="Right">Right Button</Button>
        <Button>Remaining Space</Button>
    </DockPanel>
    

    停靠行为保持不变。首先停靠顶部按钮,然后是底部按钮,顶部和底部按钮之间剩余的空间会被分割,并且最后一个按钮在中间。图3-11显示了最终窗口。

    3.7 小结

    本章详细介绍了WPF布局模型,并讨论了如何以堆栈、网格以及其他排列方式放置元素。可使用嵌套的布局容器组合创建更复杂的布局,可结合使用GridSplitter对象创建可变的分割窗口。本章一直非常关注这一巨大变化的原因——WPF布局模型在保持、加强以及本地化用户界面方面所具有的优点。

    布局内容远不止这些。接下来的几章还将列举更多使用布局容器组织元素分组的示例,还将学习允许在窗口中排列内容的几个附加功能:

    • 特殊容器。可以使用ScrollViewer、TabItem以及Expander控件滚动内容、将内容放到单独的选项卡中以及折叠内容。与布局面板不同,这些容器只能包含单一内容。不过,可以很容易地组合使用这些容器和布局面板,以便准确实现所需的效果。第6章将尝试使用这些容器。
    • Viewbox。需要一种方法来改变图形内容(如图像和矢量图形)的尺寸吗?Viewbox是另一种特殊容器,可以帮助您解决这一问题,而且Viewbox控件内置了缩放功能。在第12章中,您将首次接触到Viewbox容器。
    • 文本布局。WPF新增了用于确定大块格式化文本布局的新工具。可使用浮动图形和列表,并且可以使用分页、分列以及更复杂、更智能的换行功能来获得非常完美的结果。第28章将介绍文本布局的方式。
  • 相关阅读:
    HDU 5273 区间DP
    【管理心得之八】通过现象看本质,小王和小张谁更胜任?
    【Unity 3D】学习笔记四十二:粒子特效
    linux kernel的cmdline參数解析原理分析
    adoquery.refresh和adoquery.query的区别
    Delphi中的Sender:TObject对象解析
    ADODataSet与ADOQuery的区别
    visual studio 和 sql server 的激活密钥序列号
    修改VCL源码实现自定义输入对话框
    WINFORM 多条件动态查询 通用代码的设计与实现
  • 原文地址:https://www.cnblogs.com/shenhuanjie/p/11533920.html
Copyright © 2020-2023  润新知