11.1 基本概念和方法

Qt 布局管理器是 Qt 图形用户界面设计的基本内容,在前面的几章中也断断续续的讲到 了 Qt 的布局管理器的使用,也做了一些简单的应用。本小节将详细介绍 Qt 的布局管理器,详细阐述 Qt 布局管理器的一些特性。

11.1.1 摆放窗口部件的方法

Qt 提供了几种在窗口部件上管理子窗口部件的基本方式。

一共有 3 种方法用于管理窗体上子窗口部件的布局:绝对位置法、人工布局法和布局管理器法。

相比于使用固定尺寸和位置,布局提供了功能强大且极具灵活性的另一种方案。使用 布局后,编程人员无需计算尺寸和位置,布局可以自动进行调整,符合用户屏幕、语言以及 字体的要求。

1.绝对位置法

这种方法是最原始的摆放窗口部件的方法,甚至都不能称其为 “摆放”。它对窗体的 各个子窗口部件分配固定的大小和位置,是通过调用基类 QWidget 提供的 setGeometry()函 数来实现的。

绝对位置法有很多缺点:

  • 用户无法改变窗口的大小,当父窗口改变时,子窗口不能做出相应的调整。
  • 如果用户选择了一种不大常用的大字体,或者当应用程序被翻译成另外一种语言 时,也许会把一些文本截断。
  • 对于某些风格的平台,这些窗口部件可能会具有并不合适的尺寸大小。
  • 必须人工计算这些位置和大小。这样做不仅非常枯燥而且极易出错,并且还会让后 期的维护工作变得痛苦万分。

很显然,使用这种方式管理 GUI 应用程序大大降低了程序员的开发效率,降低了应用 程序的质量和适应性。

2.人工布局法

这种方法的核心是通过重载 QWidget::resizeEvent(QResizeEvent*)函数来使得子窗口 的的大小尺寸总是和父窗口的大小成比例,也就在一定程度上减轻了计算量,但是在其中也 要通过 setGeometry()函数来设置子窗口部件的位置和大小。

在程序的规模比较小,并且不需要时常变更设计的情况下,绝对位置法勉强可以胜 任。但是它就像前面的绝对位置法一样,仍然需要计算许多手写代码中的常量,尤其是当设 计被改变的时候,这种情况更加突出,而且它并没有消除文本会被截断的危险。辅以社会自 子窗口部件的大小提示,应该可以规避这种风险,但是这样会使代码变得尤为复杂。

3.布局管理器法

这种方式是使用 Qt 设计用户界面、组织管理 Qt 窗口部件的最好方法。布局管理器为 窗口部件提供了有感知的默认值( sensible default sizes),可以随着窗口部件大小的变 化,对子窗口部件的大小和位置做出适当的调整。

11.1.2布局管理器

Qt 的布局管理器负责在父才窗口部件区域内构建子窗口部件。这些管理器可以使其中 的窗口部件自动定位并重新调整子窗口部件、保持窗口部件敏感度最小化的变化和默认尺 寸,并可在内容或文本字体更改时自动重新定位。在 Qt Designer 中,完全可以使用布局管理器来定位控件。图 1-1 显示了使用了顶层布局管理器的应用程序在不同尺寸下的情形,可以看到,由于布局管理器的存在,使得窗口部件保持了整体的布局。

图 11-1a 图 11-1b

图 11-1 以不同尺寸显示的同一对话框

1.布局类的继承关系

QLayout 类是 Qt 的几何管理器的基类,它派生自 QObject 类和 QLayoutItem 类,是一 个抽象基类,必须被派生类所重新实现。它的派生类主要有 QBoxLayout, QGridLayout, QFormLayout 以及 QStackedLayout。而 QBoxLayout

又有两个主要的 Qt4 子类,QHBoxLayout 和 QVBoxLayout。它们之间的继承关系如图 11-2 所示,图中的抽象类用斜体表示。

  • QObject
    • QLayout
      • QGridLayout
      • QHBoxLayout

图 11-2 Qt4 布局管理器类继承关系图

QLayout 也提供了一些有用的方法,如 setSizeConstraint() 和 setMenuBar()等,但是我们很少会用到它们。

2.内建布局管理器

Qt 的内建布局管理器可以将窗口部件以及其他布局排列为横向,纵向,网格以及表单中。

如前所述,在截至目前的 Qt 4.5 中,主要提供了 5 种内建的布局管理器。

(1) 水平布局管理器(QHBoxLayout) 按从左至右的顺序将管理的窗口部件横放在一行中。

图 11-3 显示了使用水平布局管理器的窗口的大致情形。

图 11-3 使用 QHBoxLayout 布局管理器排列的窗口部件

(2) 垂直布局管理器(QVBoxLayout) 按从上至下的顺序将管理的窗口部件竖放在一列中。

图 11-4 显示了使用水平布局管理器的窗口的大致情形。

图 11-4 使用 QVBoxLayout 布局管理器排列的窗口部件

(3) 栅格布局管理器(QGridLayout) 把管理的窗口部件放在可扩展的单元格中,这样一来,如有必要, 窗口部件可以跨越多个单元。

图 11-5 显示了使用水平布局管理器的窗口的大致情形。

图 11-5 使用 QGridLayout 布局管理器排列的窗口部件

(4) 表单布局管理器(QFormLayout) 表单布局管理器主要用作管理界面上的输入窗口部件( input widgets)以及和它们相连的标签窗口部件(labels)。

图 11-6 显示了使用水平布局管理器的窗口的大致情形。

图 11-6 使用 QFromLayout 布局管理器排列的窗口部件

(5) 栈布局管理器(QStackedLayout) 按照一种类似于栈的方式排列窗口部件,在某一时刻只有一个窗口部件是可见的。 图 11-7 显示了使用水平布局管理器的窗口的大致情形。

图 11-7 使用 QStackedLayout 布局管理器排列的窗口部件

小贴士:截至目前的 4.5.2 版,Qt Designer 的窗口部件盒没有可视化的提供对栈布局管理 器的支持,不过它提供了一个栈部件 QStackedWidget,作用与栈布局管理器类似。因此, 在使用 Qt Designer 绘制 GUI 界面时,完全可以使用 QStackedWidget 来代替 QStackedLayout。

每个内建布局管理器均支持窗口部件在分配的范围内横向/纵向对齐,这样只需使用简单的布局和对齐属性,即可自定义用户界面的外观。

除了上述内建的布局管理器外,分裂器也是一种常见的布局管理器,它是由 QSplitter 类实现的。在 Qt Designer 的窗口部件盒中也没有为它提供可视化的部件,不过却提供了 鼠标右键上下文菜单项来实现该布局。在 Qt 4.5 的后续版本中,它有望“转正”,成为正 式的内建布局管理器。在本章后面的内容中会详细的讲到如何使用分裂器布局。分裂器布局 又分为分裂器水平布局和分裂器垂直布局,图 11-8 和图 11-9 分别显示了它们的大致情 形。

图 11-8 使用分裂器水平布局管理器排列的窗口部件

图 11-9 使用分裂器垂直布局管理器排列的窗口部件

在实际的应用程序开发中,很少有只用到单一布局的情况,多数情况下需要组合或者 嵌套应用布局。Qt4 中的各种布局可以被组合或嵌套至任意的级别中,在后面的例子中将为 大家展示如何将基本布局综合使用起来。

11.1.3优化布局结构

在迄今为止讲到每一个例子中,我们只是简单的把窗口部件放置到某个确定的布局中。但在某些情况下,由此形成的布局看起来可能还不是我们最想要的形式。在这些情形中,可以通过改变要摆放的窗口部件的大小策略和大小提示来调整布局。

1.大小提示(size hint)和最小大小提示(minimum size hint)

在介绍 Qt 窗口部件的大小策略之前,首先介绍大小提示( size hint)和最小大小提 示(minimum size hint)。

(1) 大小提示

大小提示是 Qt 为一个窗口部件推荐的尺寸。当 Qt GUI 窗口部件进行初始化时,将通 过 QWidget::sizeHint()来获得窗口部件的大小提示,这是一个虚函数,它的原型为:

virtual QSize sizeHint () const

在未被重载的情况下,它的返回值是这样的:

  • 如果该窗口部件不属于任何布局管理器,那么该函数将返回一个无效的值;
  • 如果该窗口部件属于某个布局管理器,那么该函数将返回一个该布局管理器认为比 较合适的尺寸。

(2) 最小大小提示

最小大小提示(minimum size hint)是 Qt 为窗口部件推荐的最小尺寸,它的使用规 则是:

如果需要绘制的窗口部件的尺寸(包括长和高两个方面)小于其最小提示(这在 Qt Designer 中往往表现为有些被压缩的看不到它的内容),并且该窗口部件的最小提示在最 大尺寸和最小尺寸允许的范围内,那么该窗口部件显示的尺寸将是其最小提示的值。

设置窗口部件的最小大小提示是通过 QWidget::minimumSizeHint()完成的。它的返回 值有如下情景:

  • 如果该窗口部件没有布局管理器,该函数返回一个无效的值;
  • 如果该窗口部件属于某个布局管理器,该函数返回布局管理器认为合适的一个尺 寸。

2.大小策略(size policy)

一个窗口部件的大小策略会告诉布局系统应该如何对它进行拉伸或收缩。 Qt 为它所有的内置窗口部件都提供了合理的默认大小策略值,但是由于不可能为每一种可能产生的布局都提供唯一的默认值,所以在一个窗体中,开发人员改变它上面的一个或两个窗口部件的大 小策略是非常普遍的现象。一个 QSizePolicy 既包含一个水平分量也包含一个垂直分量。 以下是一些常用的取值:

表 11-1 枚举值 QSizePolicy::Policy 的内容

枚举常量 说明
QSizePolicy::Fixed 0 大小提示是该窗口部件的唯一尺寸选择,所以它不会 发生任何的伸缩。
QSizePolicy::Minimum GrowFlag 大小提示是该窗口部件的最小尺寸,它不会变得更 小,但它可以变得更大,不过采用该策略的窗口部件 在“争夺”空间上不占优势。
QSizePolicy::Maximum ShrinkFlag 大小提示是该窗口部件的最大尺寸,也就是该窗口部 件不会比大小提示的尺寸更大。该窗口部件可以在没 有受到其它窗口部件“要求”的情况下,自由的缩小 尺寸。
QSizePolicy::Preferred GrowFlag | ShrinkFlag 一般情况下,该窗口部件会将大小提示作为它的优先 和最佳选择,但它也可以变得足够的小,也可以变大,但不占优势。该策略是 QWidget 窗口部件默认的 策略。
QSizePolicy::Expanding GrowFlag | ShrinkFlag | ExpandFlag 采用该策略的窗口部件也能够感觉到尺寸提示,但是 它倾向于尽可能的占用更大的空间,该窗口部件也可 以变得足够小。
QSizePolicy::MinimumExpanding GrowFlag | ExpandFlag 大小提示将是该窗口部件的最小尺寸,该窗口部件将 尽可能的占用更多的空间。该策略已经不再被推荐使 用,建议用 Expanding 替代它,并且重载 minimumSizeHint()。
QSizePolicy::Ignored ShrinkFlag | GrowFlag | IgnoreFlag 与 Expanding 有些相似,只是所有的大小提示都被忽 略,该窗口部件将会尽可能的占用空间。

表 11-1 中的“值”这一列实际上告诉了我们每一种策略一般是具有 “倾向性”的,比

如 QSizePolicy::Fixed 的值为 0,则它“倾向于”保持自己的大小不变,即保持大小提示 的尺寸。而 QSizePolicy::Expanding 的值是 3 个值的叠加,总的“倾向性”是趋于占用更 多空间的,等等。这就为当多个具有不同大小策略的窗口部件放置在一起时,如何判断它们 占用空间的模式提供了基本的判断依据,以下是几种常见的组合。

相同大小策略的窗口部件被布局管理器组合在一起。在这种情况下,除了窗口部件 不能超出它的大小范围外,不同的窗口部件可以按自己的伸缩因子在其允许的范围内自由的 伸缩。

QSizePolicy::Fixed 和任何其他的大小策略组合在一起。

具有 QSizePolicy::Fixed 大小策略的窗口部件其大小是不变的,即保持在 sizeHint() 大小,而其他的窗口部件可以在允许的范围内自由伸缩。

QSizePolicy::Preferred 和 QSizePolicy::Expanding 组合在一起。

具有 QSizePolicy::Preferred 尺寸策略的窗口部件其大小是不变的,即它认为大小提 示是最适合它的,而其他的窗口部件大小可以在其允许的范围内自由伸缩。

QSizePolicy::Ignored 和其他尺寸策略(QSizePolicy::Fixed 策略除外)组合在 一起的时候,不同的窗口部件在各自允许的范围内自由伸缩。

QSizePolicy::Preferred,QSizePolicy::Minimum 和 QSizePolicy::Maximum 组合 在一起的时候,各窗口部件在各自允许的范围内可以自由伸缩。

3.伸缩因子(stretch factor)

除了大小策略中包含的水平方向和垂直方向两个分量之外, QSizePolicy 类还保存了水平方向和垂直方向的一个伸缩因子。这些伸缩因子可以用来说明在增大窗体时,对不同的子窗口部件应使用的不同放大比例。即需要设置 QSizePolicy::horizontalStretch 和 QSizePolicy::verticalStretch 的值来实现。默认情况下,被布局管理器组合在一起的窗口部件的伸缩因子是相等的,都为 0。此时,在所有的窗口部件都没有超出各自的大小范围 允许的情况下,窗口部件的大小始终相等。

例如,假定在一个 QListWidget 的右面还有一个 QTextEdit,并且希望这个 QTextEdit 的长度能够是 QListWidget 长度的两倍,那么就可以把这个 QTextEdit 在水平方向上的拉 伸因子(QSizePolicy::horizontalStretch)设置为 2,而把 QListWidget 在水平方向上的 拉伸因子(QSizePolicy::horizontalStretch)设置为 1;垂直方向上保持默认为 0,即两 者一样的高。这样设置的效果如图 11-10 所示。

图 11-10 设置伸缩因子后窗体的效果

4.大小约束(size constraint)

影响布局方式的另一种方法是设置它的子窗口部件的最大大小、最小大小或固定大小。这些是通过设置 sizeConstraint 属性来完成的。该属性值是一个枚举常量,定义了布局的大小约束的模式。表列出了它所有可能的取值,它的默认值是 QLayout::SetDefaultConstraint。获取和设置该属性值可以通过 QWidget::layout()来获取主窗口部件的布局管理器,然后可以调用 QLayout::sizeConstraint()函数来查看当前的设置情况,然后再通过 QLayout::setSizeConstraint()函数来设置该布局管理器的 sizeConstraint 属性。这两种函数的原型如下:

SizeConstraint sizeConstraint () const void setSizeConstraint ( SizeConstraint )

其中,SizeConstraint 的取值即是在表 11-2 中的枚举值的范围内。

表 11-2 布局管理器的大小约束属性(QLayout::SizeConstraint)可能的取值

常量 说明
QLayout::SetDefaultConstraint 0 主窗口部件的最小尺寸设置为 minimumSize(),除非该窗口部件已经有一个 最小尺寸
QLayout::SetFixedSize 3 主窗口部件的尺寸设置为 sizeHint(),并且不允许改变该窗口部件的尺寸
QLayout::SetMinimumSize 2 主窗口部件的最小尺寸设置为 minimumSize(),并且该窗口部件不能够变得 更小
QLayout::SetMaximumSize 4 主窗口部件的最大尺寸设置为 maximumSize(),并且该窗口部件不能够变得 更大
QLayout::SetMinAndMaxSize 5 主窗口部件的最小尺寸设置为 minimumSize(),最大尺寸设置为 maximumSize()
QLayout::SetNoConstraint 1 主窗口部件的大小不会受到约束

5.空白(margin)和间距(spacing)

每种布局都有两个重要的属性,空白和间距。空白指的是整个布局四周距离窗体边缘的距离;间距指的是布局管理器内部各个窗口部件之间的距离。

空白属性即 margin(),间距属性即 spacing(),它们的默认值是有窗体的风格决定 的。Qt 的默认风格下,子窗体部件的 margin()的值是 9 英寸,窗体的 margin()值是 11 英 寸。spacing()的值与 margin()相同。

如果要设置这两个值可以通过 setMargin()和 setSpacing()。

注意,从 Qt4.3 开始,margin()属性已经逐渐不再被 Qt4 所推荐,更好的设置空白的 方法是使用 setContentsMargins()方法,它的原型如下:

void QLayout::setContentsMargins ( int left, int top, int right, int bottom )

其中,left, top, right, 和 bottom 表示环绕在布局周围的空白。

对于 QGridLayout 和 QFormLayout,不要使用 setSpacing()方法,而是要分别使用 setHorizontalSpacing()和 setVerticalSpacing()方法来设置水平和垂直方向的间距。如 果你使用了 setSpacing()方法,获取 spacing()时,它的返回值将为-1。