本文转自:《Qt编程指南》 作者:奇先生 Qt编程指南,Qt新手教程,Qt Programming Guide 6.5 控件尺寸调整策略
6.5.1 布局器工作原理
这些函数和属性我们在 6.1.1 小节专门介绍过,设置最小尺寸和最大尺寸是没啥技术含量的,比较简单,这里不赘述了。 6.5.2 QSizePolicy 之一:伸展因子
|
枚举标志位 | 数值 | 描述 |
QSizePolicy::GrowFlag | 1 | 可增长标志,如果有必要的话,可以在建议尺寸之外继续增长。 |
QSizePolicy::ExpandFlag | 2 | 尽量扩展标志,能占多大空间就占多大。 |
QSizePolicy::ShrinkFlag | 4 | 可收缩标志,如果有必要的话,可以在缩小到建议尺寸之后继续缩小。 |
QSizePolicy::IgnoreFlag | 8 | 忽略建议尺寸,这个增长方式最野蛮,能占多大空间就占多大空间 |
建议尺寸就是通过控件的 sizeHint() 函数获取的尺寸,这个尺寸通常由 Qt 类库自己根据要显示的内容计算。建议尺寸是伸展策略的基准。
控件通常不会直接设置策略的基本标志位,因为没有这方面的设置函数。基本标志位的用途,是为了组合成为实用的策略枚举常量,也就是下面第二层级的内容。
伸展策略的枚举常量由 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 | 主动扩张 | 以建议尺寸作为最小尺寸,主动扩张,尽可能占据新增的区域。 |
QSizePolicy::Ignored | ShrinkFlag | GrowFlag | IgnoreFlag | 野蛮扩张 | 忽略建议尺寸,虽然能屈能伸,但是它会尽最大可能占据空间。 |
我们对七个策略常量大致分两类,第一类是固定、单向缩小、单向拉大的,相同布局情景中,占据的尺寸大小排序为:
QSizePolicy::Maximum ≤ QSizePolicy::Fixed ≤ QSizePolicy::Minimum ≤ QSizePolicy::MinimumExpanding 。
第二类是能屈能伸的,如果在相同布局情景中,占据尺寸大小排序为:
QSizePolicy::Preferred ≤ QSizePolicy::Expanding ≤ QSizePolicy::Ignored 。
七个策略枚举常量,最常用到的只有如下四个,我们考虑它们在相同布局场景中,占据的尺寸大小进行不严格排序(有例外):
QSizePolicy::Fixed ≤ QSizePolicy::Preferred ≈ QSizePolicy::Minimum ≤ QSizePolicy::Expanding
虽然 Preferred 和 Expanding 都是能屈能伸的类型,但实际情况是只有窗口缩小到特别小的情况,这两个才会比 Fixed 小。
窗口如果特别小,那么窗口的可用性显然受限,这通常属于不合理的设置,因此正常情况下不会遇到 Preferred 和 Expanding 比 Fixed 占用空间小的情况。
以上的策略枚举常量是用于尺寸策略的设置函数中,控件和窗口的伸展策略细分为水平方向和垂直方向,通过如下两个函数分别设置:
- void QSizePolicy::setHorizontalPolicy(Policy policy) //设置水平策略
- void QSizePolicy::setVerticalPolicy(Policy policy) //设置垂直策略
水平和垂直策略在大多数情况下都是不相关的,各自管各自的维度。除了调用函数,设计师和 QtCreator 设计模式也可以直接设置控件的 sizePolicy 属性两个子属性:"水平策略" 和 "垂直策略" 。
伸展策略的常量有七个,每个常量都有各自的特性,我们在这里把它们简化一下,在实际使用中可以按照下面三条建议来运用策略的枚举常量:
① 如果希望控件尺寸在水平或垂直方向固定住,那么把该维度的策略设置为 QSizePolicy::Fixed。
② 如果希望控件被动拉伸,其他控件不需要空间时这个控件才会占据新增区域,那么可以用 QSizePolicy::Preferred (尺寸下限是隐含的最小建议尺寸)或者 QSizePolicy::Minimum(尺寸下限是建议尺寸)。
③ 如果希望控件尽量拉伸,主动扩张,那就把策略设置为 QSizePolicy::Expanding。
Qt 里面的控件默认策略也是基本符合上面三条建议的,所以希望大家记住这三条建议,因为比较实用。
QSizePolicy 除了上面两小节的伸展因子和伸展策略,还有一些其他的内容,这部分补充内容应用会比较少,但是会影响界面的一些细节,有必要在这讲一下。
QSizePolicy::ControlType 枚举类型有一大堆枚举常量,大部分的 Qt 控件都对应一个枚举常量,比如按压按钮对应的常量为 QSizePolicy::PushButton,单行编辑控件对应的枚举常量是 QSizePolicy::LineEdit,类似的还 有很多,这里不列举了。
QSizePolicy 类中关于控件类型获取和设置的函数为:
- ControlType QSizePolicy::controlType() const
- void QSizePolicy::setControlType(ControlType type)
这个控件类型应用比较少,不是什么时候都生效的,而且对界面布局影响很小,只是一些细节有差异。控件类型只会被一些特定的 Qt 界面风格(可以查询 QStyle 类文档)采用,比如苹果系统风格的 QMacStyle,不同的控件类型会影响各个控件之间的默认间隙。比如 Mac OS X Aqua 指导方针中指出按压按钮之间需要 12 像素的间隙,而垂直方向排布的单选按钮间隔是 6 像素。因为控件类型影响很小,所以通常可以忽略这个设置。
多数情况下建议尺寸 sizeHint() 的高度和宽度是不相关的,但有些特殊情况,比如能够自动换行的标签控件、菜单栏(后面章节讲解),比如一行长文本自动换行变成两行时,高度是双倍的,如果把标签拉宽,当两 行文本恢复成一行的时候,高度就变成单行的。这种控件越宽,它高度相对低一些,越窄,高度就高一些。因此这些控件的建议尺寸计算时,高度和宽度是相关的。
可以通过如下函数设置在计算建议尺寸时,高度和宽度相关:
void QSizePolicy::setHeightForWidth(bool dependent) //设置高度依赖宽度
dependent 如果为 true,那么控件的建议尺寸高度就和宽度相关。如果 dependent 为 false 那么就是无关的。
如果要获知建议尺寸的高度是否与宽度相关,可以用如下函数:
bool QSizePolicy::hasHeightForWidth() const //判断高度是否依赖宽度
另外还有一对相反功能的函数,计算建议尺寸时,宽度可能会依赖高度,这个设置只对 QGraphicsLayout 的子类有用,一般是用不到的:
- void QSizePolicy::setWidthForHeight(bool dependent) //设置宽度依赖高度
- bool QSizePolicy::hasWidthForHeight() const //判断宽度是否依赖高度
HeightForWidth 和 WidthForHeight 二者最多只能有一个生效,不能双向依赖的。
注意无论是 HeightForWidth 还是 WidthForHeight ,都只对建议尺寸的计算有影响,不会直接影响窗口或控件的高度和宽度拉伸比例。如果希望窗口或控件的高度和宽度保持一定比例,比如 2 : 3 ,那么这些函数是完全没用的,因为根本不是一个概念。
程序运行时,控件都可以通过函数 hide() 隐藏自己。在控件隐藏时,控件是否还占据布局器里的空间,这是可以设置的:
- void QSizePolicy::setRetainSizeWhenHidden(bool retainSize) //设置控件在隐藏时是否仍占据布局器空间
- bool QSizePolicy::retainSizeWhenHidden() const //判断隐藏控件是否占据布局器空间
默认情况下,控件调用 hide() 隐藏之后,就不会在通过布局器分配空间了,因为没有必要。
如果遇到特殊情况需要保留隐藏控件在布局器里的占用的空间,可以用上述函数设置。
如果设置保留隐藏控件的空间,那么布局器会留下一块空白区域,就是控件在隐藏前应该占据的区域。
为了应对可能的屏幕旋转操作,QSizePolicy 提供了一个快捷函数,能够把水平方向的伸展因子、伸展策略与垂直方向上的伸展因子、伸展策略完全互换过来:
void QSizePolicy::transpose()
这个函数读者可以根据实际情况试试。
关于控件的尺寸调整策略就介绍这么多,下面通过两个例子试一试伸展因子和伸展策略的功效。
这一小节通过两个窗口来对比常用到的四个伸展策略枚举常量,主界面的窗口里面是六个按钮,点击里面的第一个按钮会弹出第二个示范窗口,第二个示范窗口里面放置六个 单行编辑控件。两个窗口的布局器和各个控件的水平伸展策略都是一样的,我们一方面对比四个常用策略的拉伸特性,另一方面对比按钮和单行编辑控件对布局器和策略的不 同反应。
打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 comparepolicies,创建路径 D:\QtProjects\ch06,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,按照下图拖入六个按钮:
我们按从上到下、从左到右顺序说明一下六个按钮的属性:
① 文本 "Fixed" ,对象名称 pushButtonFixed ,水平策略选择 Fixed 。
② 文本 "Preferred" ,对象名称 pushButtonPreferred,水平策略选择 Preferred 。
③ 文本 "Preferred2" ,对象名称 pushButtonPreferred2,水平策略选择 Preferred 。
④ 文本 "Minimum" ,对象名称 pushButtonMinimum,水平策略选择 Minimum 。
⑤ 文本 "Minimum2" ,对象名称 pushButtonMinimum2,水平策略选择 Minimum 。
⑥ 文本 "Expanding" ,对象名称 pushButtonExpanding,水平策略选择 Expanding 。
设置好六个按钮的属性之后,我们对每个行进行布局,先对第一行两个按钮水平布局,第二行和第三行也一样用水平布局,得到的效果如下:
然后我们点击主窗体空白区域,不选中任何控件和子布局器(其实就是唯一选中主界面窗口自身),直接点击上面的垂直布局按钮,自动为窗口设置主布局器:
这样主界面的布局就设置完毕。下面我们要为第一个 "Fixed" 按钮添加一个 clicked() 信号的槽函数,等会我们在槽函数添加代码来弹出第二个示范窗口:
添加好槽函数之后,保存界面文件,我们回到代码编辑模式,首先是编辑主窗体头文件 widget.h ,我们要为第二个示范窗口添加成员指针和创建第二个示范窗口的函数:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_pushButtonFixed_clicked(); private: Ui::Widget *ui; //第二个示范窗口,全部放置 QLineEdit QWidget *m_pWidget; //通过代码构造第二个示范窗口 void CreateWidget(); }; #endif // WIDGET_H
m_pWidget 是我们自己用代码构造的第二个示范窗口,CreateWidget() 就是负责构造第二个示范窗口的函数。头文件里其他代码行都是自动添加的。
主窗体的界面不需要调整,我们刚才在设计模式都设置好了。我们下面编辑源代码文件 widget.cpp 主要是为了构建第二个示范窗口的内容,并通过 "Fixed" 按钮的槽函数弹窗显示。
在 widget.cpp 文件中,首先是头文件包含和主窗体的构造函数:
#include "widget.h" #include "ui_widget.h" #include <QLineEdit> //单行编辑器 #include <QHBoxLayout> //水平布局器 #include <QVBoxLayout> //垂直布局器 #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //按钮的建议尺寸和最小建议尺寸 qDebug()<<tr("Preferred 按钮:") <<ui->pushButtonPreferred->sizeHint() <<ui->pushButtonPreferred->minimumSizeHint(); qDebug()<<tr("Expanding 按钮:") <<ui->pushButtonExpanding->sizeHint() <<ui->pushButtonExpanding->minimumSizeHint(); m_pWidget = NULL; //初始空指针 CreateWidget(); //构建第二个示范窗口 }
我们新增了三个头文件包含,是第二个示范窗口要用到的单行编辑器和水平布局器、垂直布局器。
在构造函数里面,我们首先打印了 "Preferred" 按钮和 "Expanding" 按钮的建议尺寸和最小建议尺寸。
因为这两个按钮都是能伸能屈的,建议尺寸是最优的尺寸,但是在需要伸缩的情况下,这两个按钮可能变大也可能变小。
我们在设计模式没有设置按钮的最小尺寸 minimumSize,在这种情况下,minimumSizeHint() 尺寸会自动成为尺寸下限,按钮缩小到这个最小建议尺寸,就不会再缩小。
打印按钮的信息之后,我们将 m_pWidget 初始化为空指针,然后调用 CreateWidget() 构建第二个示范窗口的界面。CreateWidget() 函数代码等会讲解。我们先看看析构函数的内容:
- Widget::~Widget()
- {
- //删除第二个窗口
- if(m_pWidget != NULL)
- {
- delete m_pWidget; m_pWidget = NULL;
- }
- //删除ui
- delete ui;
- }
析构函数中,我们对 m_pWidget 做了判断,如果是非空指针,就删除第二个示范窗口,并把 m_pWidget 设为空。
析构函数最后一句是原本自动生成的代码,不用变。
接下来是 CreateWidget() 函数,就是构建第二个示范窗口的代码,我们没有用新的 ui 文件,直接用代码构建该示范窗口。这个函数内容比较多,我们按照几个小块来分开讲解,首先是窗口新建和主布局器新建:
//建立第二个示范窗口,包括内部的单行编辑控件和布局器
- //仿造主界面的架构,只是把按钮换成单行编辑器
- void Widget::CreateWidget()
- {
- //构建第二个示范窗口
- m_pWidget = new QWidget(this, Qt::Window); //独立窗口
- m_pWidget->resize(480, 360);
- m_pWidget->setWindowTitle(tr("单行编辑器的布局"));
- //主布局器是垂直排列的三行
- QVBoxLayout *mainLayout = new QVBoxLayout(m_pWidget);
- //待续
主界面是不同水平伸展策略的按钮,我们这第二个示范窗口则是不同水平伸展策略的单行编辑器。
CreateWidget() 函数内,第一句是新建一个独立的子窗口用于弹窗,注意 QWidget() 构造函数第二个参数,是窗口类型标志位,如果不设置标志位,那么 m_pWidget 会作为大部件显示在主窗体内部,而在设置标志位是 Qt::Window 或 Qt::Dialog ,那么新建的 m_pWidget 才是独立的子窗口,不会放在主窗体内部。
然后我们把第二个示范窗口大小重置为 480*360 ,并设置该窗口标题为 "单行编辑器的布局" 。
接着新建了主布局器,用于容纳三个水平行的布局器。主布局器构造时指定了 m_pWidget 为父窗口指针。
下面来看看第一行的控件和布局器代码:
//构建六个单行编辑器,分成三行做对比 //第一行 //第一个是固定尺寸的 QLineEdit *leFixed = new QLineEdit(m_pWidget); leFixed->setText(tr("Fixed")); QSizePolicy sp = leFixed->sizePolicy(); //修改第一个的水平策略为 Fixed sp.setHorizontalPolicy(QSizePolicy::Fixed); leFixed->setSizePolicy(sp); //第二个编辑器 QLineEdit *lePreferred = new QLineEdit(m_pWidget); lePreferred->setText(tr("Preferred")); sp = lePreferred->sizePolicy(); //修改第二个的水平策略为 Preferred sp.setHorizontalPolicy(QSizePolicy::Preferred); lePreferred->setSizePolicy(sp); //第一行的布局器 QHBoxLayout *lay1 = new QHBoxLayout(); lay1->addWidget(leFixed); //添加第一个编辑器 lay1->addWidget(lePreferred); //添加第二个编辑器 //把第一行的布局器添加到主布局器 mainLayout->addLayout(lay1); //待续
第一个单行编辑器新建时是指定 m_pWidget 为父窗口,这样该控件会由 m_pWidget 窗口管理。
然后设置第一个单行编辑器的文本为 "Fixed" ,并把水平策略修改为 QSizePolicy::Fixed 。
第一个编辑器的尺寸会固定为建议尺寸,不会被拉宽。
第二个单行编辑器新建时也是指定 m_pWidget 为父窗口,然后设置文本为 "Preferred",修改水平策略为 QSizePolicy::Preferred,这个编辑器会被动地拉大或缩小。虽然没有指定该编辑器最小尺寸,但是等会程序运行时我们会看到 "Preferred" 编辑器以隐含的最小建议尺寸为下限。
然后我们新建了第一行的水平布局器 lay1,注意这个布局器没有指定父窗口。
我们把第一行的两个编辑器添加给 lay1 ,然后把 lay1 添加到主布局器。
下面看看第二行控件和布局器的代码:
//第二行 //第三个编辑器 QLineEdit *lePreferred2 = new QLineEdit(m_pWidget); lePreferred2->setText(tr("Preferred2")); sp = lePreferred->sizePolicy(); //修改第三个的水平策略为 Preferred sp.setHorizontalPolicy(QSizePolicy::Preferred); lePreferred2->setSizePolicy(sp); //第四个编辑器 QLineEdit *leMinimum = new QLineEdit(m_pWidget); leMinimum->setText(tr("Minimum")); sp = leMinimum->sizePolicy(); //修改第三个的水平策略为 Minimum sp.setHorizontalPolicy(QSizePolicy::Minimum); leMinimum->setSizePolicy(sp); //第二行的布局器 QHBoxLayout *lay2 = new QHBoxLayout(); lay2->addWidget(lePreferred2); lay2->addWidget(leMinimum); //添加到主布局器 mainLayout->addLayout(lay2); //待续
第三个单行编辑器也是以 m_pWidget 为父窗口,设置文本为 "Preferred2",修改水平策略为 QSizePolicy::Preferred。
第四个单行编辑器也是以 m_pWidget 为父窗口,设置文本为 "Minimum",修改水平策略为 QSizePolicy::Minimum。
然后新建了第二行的布局器 lay2 ,这个布局器没有指定父窗口指针。
接着将 lePreferred2 和 leMinimum 两个编辑器添加给布局器 lay2 ,再把第二行布局器 lay2 添加到主布局器 mainLayout 。
接下来是第三行控件和布局器的代码:
//第三行 //第五个编辑器 QLineEdit *leMinimum2 = new QLineEdit(m_pWidget); leMinimum2->setText(tr("Minimum2")); sp = leMinimum2->sizePolicy(); //修改第五个的水平策略为 Minimum sp.setHorizontalPolicy(QSizePolicy::Minimum); leMinimum2->setSizePolicy(sp); //第六个编辑器 QLineEdit *leExpanding = new QLineEdit(m_pWidget); leExpanding->setText(tr("Expanding")); sp = leExpanding->sizePolicy(); //修改第六个的水平策略为 Expanding sp.setHorizontalPolicy(QSizePolicy::Expanding); leExpanding->setSizePolicy(sp); //第三行的布局器 QHBoxLayout *lay3 = new QHBoxLayout(); lay3->addWidget(leMinimum2); lay3->addWidget(leExpanding); mainLayout->addLayout(lay3); //待续
第五个单行编辑器也是以 m_pWidget 为父窗口,设置文本为 "Minimum2" ,修改水平策略为 QSizePolicy::Minimum。
第六个单行编辑器也是以 m_pWidget 为父窗口,设置文本为 "Expanding",修改水平策略为 QSizePolicy::Expanding。
然后新建了第三行的布局器 lay3 ,这个布局器没有指定父窗口。
接着把第三行的两个编辑器 leMinimum2、leExpanding 添加到 布局器 lay3 ,并把 lay3 添加到主布局器 mainLayout。
注意上面关于控件的代码写法:
单行编辑控件的父窗口是 m_pWidget ,而不是布局器,也不能是布局器。
布局器仅仅是辅助布局的手段,布局器不是实际的功能控件。
布局器基类是 QLayoutItem,不能独立存在,它需要依附于其他实体功能控件或窗口,但不会拥有任何控件,也就是不能作为父窗口。
实体功能控件的基类是 QWidget,可以独立存在,也可以借助布局器进行布局,QWidget 派生类控件可以作为其他控件的父窗口,可以拥有子控件。
CreateWidget() 函数最后一小部分代码是设置主布局器和打印调试信息的:
- //设置该窗口的主布局器
- m_pWidget->setLayout(mainLayout);
- //如果只有一个布局器的 parent 设置为该窗口,那么可以不调用 setLayout()
- //上面的 setLayout() 一句其实可以省略,mainLayout 自动是主布局器
- //打印信息
- qDebug()<<tr("Fixed 编辑器建议尺寸:")<<leFixed->sizeHint();
- qDebug()<<tr("Preferred 编辑器建议尺寸:")<<lePreferred->sizeHint();
- qDebug()<<tr("Preferred 编辑器最小建议尺寸:")<<lePreferred->minimumSizeHint();
- qDebug()<<tr("Minimum 编辑器建议尺寸:")<<leMinimum->sizeHint();
- qDebug()<<tr("Expanding 编辑器建议尺寸:")<<leExpanding->sizeHint();
- qDebug()<<tr("Expanding 编辑器最小建议尺寸:")<<leExpanding->minimumSizeHint();
- }
通常设置某个窗口的主布局器就是调用它的 setLayout() 函数,这是主动设置窗口主布局器的方式。
还有第二种设置主布局器的方式,就是 ui_*.h 里面采用的方式,当有且只有一个布局器将父窗口指针设置为窗口 m_pWidget 时,这个布局器就自动成为 m_pWidget 的主布局器了。
我们之前代码里的 lay1、lay2、lay3 构造时都没有父窗口指针,只有 mainLayout 指定了父窗口为 m_pWidget,其实 mainLayout 已经成为 m_pWidget 主布局器了,因此上面的 setLayout() 一句代码可以省略。
最后几句 qDebug() 打印了 "Fixed" 和 "Minimum" 编辑器的建议尺寸,并打印了 "Preferred" 和 "Expanding" 编辑器的建议尺寸和最小建议尺寸。
CreateWidget() 函数代码就是上面那些。 程序两个窗口的控件个数和布局格式是一样的,但第二个示范窗口的代码其实已经不少了。而我们通过设计模式生成的主界面窗口,就没编写什么代码。可见通过 Qt 设计师和 QtCreator 设计图形界面是很方便的,节省了很多关于新建控件、布局器等重复无聊的代码。
源代码文件 widget.cpp 最后一部分是我们主界面第一个按钮的槽函数代码:
- //点击按钮弹出第二个窗口
- void Widget::on_pushButtonFixed_clicked()
- {
- if(m_pWidget != NULL)
- {
- m_pWidget->show(); //显示
- }
- }
这个槽函数非常简单,就是判断 m_pWidget 是否非空,如果非空就弹出该窗口。
操作指针之前判断一下指针非空,有利于增加程序的健壮性,以免操作空指针。
整个例子的代码到这里就完整了。主界面的窗口其实完全靠 QtCreator 设计模式完成的,而第二个示范窗口是完全靠手动编写代码实现的。下面生成并运行例子看看,先看主界面窗口:
在主界面窗口比较大时,明显看到第一行的 "Fixed" 按钮比较窄,而 "Preferred" 按钮比较宽。因为 "Fixed" 按钮宽度是固定的,不能拉伸,因此水平布局器只能尝试拉伸 "Preferred" 按钮,正好 "Preferred" 按钮能够被动拉伸,就被拉宽了。
第二行的 "Preferred" 按钮和 "Minimum" 按钮是等宽的,因为二者都是被动拉伸,谁也不占上风,就平均拉伸。
第三行的 "Minimum" 按钮属于被动拉伸,而 "Expanding" 按钮属于主动拉伸,所有额外的空间都被 "Expanding" 按钮占据了,"Minimum" 按钮仅保持了建议尺寸。
我们把主界面窗口缩到最小尺寸,可以看到下图效果:
注意,"Fixed" 按钮和 "Minimum" 按钮的尺寸下限是建议尺寸 sizeHint(),
而 "Preferred" 和 "Expanding" 按钮尺寸下限是最小建议尺寸 minimumSizeHint()。
上图中六个按钮最小情况下都是一样大的,因为按钮的建议尺寸和最小建议尺寸是一样大的,我们可以在输出面板看到按钮的尺寸信息:
- "Preferred 按钮:" QSize(75, 23) QSize(75, 23)
- "Expanding 按钮:" QSize(75, 23) QSize(75, 23)
对于按钮组成的布局器,四种常用伸展策略是符合下面公式的:
QSizePolicy::Fixed ≤ QSizePolicy::Preferred ≈ QSizePolicy::Minimum ≤ QSizePolicy::Expanding
主窗口的三行按钮对比完毕。我们下面点击主界面第一个 "Fixed" 按钮进行弹窗,看到第二个示范窗口:
在窗口比较大时,六个编辑器的拉伸特性和主窗口没区别,都符合上面的公式。
下面我们把第二个示范窗口缩到最小,再看看效果:
这时候就明显看到 "Fixed" 和 "Minimum" 编辑器保持一个建议尺寸,不会再变小。
而 "Preferred" 和 "Expanding" 编辑器已经缩到比建议尺寸更小了,这两个可伸缩的编辑器的尺寸下限是由最小建议尺寸指定的。我们可以在 调试输出信息里看到:
- "Fixed 编辑器建议尺寸:" QSize(133, 20)
- "Preferred 编辑器建议尺寸:" QSize(133, 20)
- "Preferred 编辑器最小建议尺寸:" QSize(39, 18)
- "Minimum 编辑器建议尺寸:" QSize(133, 20)
- "Expanding 编辑器建议尺寸:" QSize(133, 20)
- "Expanding 编辑器最小建议尺寸:" QSize(39, 18)
单行编辑器的建议尺寸都是 133*20,最小建议尺寸是 39*18 。
因为单行编辑器的最小建议尺寸比普通的建议尺寸更小,因此上面的公式在窗口特别小的时候不成立。
不过窗口特别小的时候,控件的可用性已经大大降低了,单行编辑器连一个单词都显示不完整了,这种情况在实际应用中是很少遇见的。
本小节的例子就是为了印证 6.5.3 小节关于常用伸展策略的三条建议,伸展策略的枚举常量原本有七个,我们把七个策略常量简化为三条使用建议,希望大家记住这三条建议,到实际布局时会比较实用。
本小节是对上一章 5.6.3 小节的图标使用示例进行布局,我们简单示范一下布局器的伸展因子和伸展策略用法。
我们复制 D:\QtProjects\ch05\ 目录里面的 helloqrc 文件夹,粘贴到第 6 章的示例目录 D:\QtProjects\ch06\ ,然后进行下面操作:
① 把新的 helloqrc 文件夹重命名为 helloqrcnew ,并删除里面的 helloqrc.pro.user 用户文件。
② 把 helloqrcnew 文件夹里面的 helloqrc.pro 重命名为 helloqrcnew.pro 。
③ 用记事本打开新的 helloqrcnew.pro 文件,修改里面的 TARGET 一行,变成下面这句:
TARGET = helloqrcnew
这样就得到新项目 helloqrcnew ,我们用 QtCreator 打开这个新项目,在配置项目界面选择所有套件并点击 "Configure Project" ,配置好项目后,打开 widget.ui 界面文件,进入 QtCreator 设计模式:
这个界面的布局思路大概是这样的,我们把上面三行用网格布局器排布,第一列和第二列的伸展因子比例设置为 1:3 ,这样在窗口拉大时,标签占 1/4,右边输入控件占 3/4。我们之前 6.3.2 和 6.4.2 小节的个人信息收集示例布局都出现了标签控件很窄,而右边输入控件很宽,看着有点别扭。通过网格布局器的伸展因子比例设置,可以把标签也拉宽些。这样在窗口比较大时,整体 界面不会太别扭。
第四行的两个按钮,我们用水平布局器封装,然后把网格布局器与按钮布局器组合成一个垂直布局器,作为窗口的主布局器。
下面我们开始操作:
(1)选中两个单选按钮,点击上面的水平布局按钮,把单选按钮组合到一块:
(2)我们选中前三行的控件和单选按钮布局器,点击上面的网格布局按钮,实现网格布局:
实现网格布局后,把该网格布局器的列伸展因子 layoutColumnStretch 调整为1,3这样标签占 1/4 ,右边一列占 3/4 。
(3)我们选中两个按压按钮,点击上面的水平布局工具按钮,实现水平布局:
(4) 我们点击主窗体空白区域,不选中任何控件(其实就是唯一选中主界面窗口自身),直接点击上面的垂直布局按钮,得到下图的效果:
目前这个界面不是我们想看到的,首先是下面的按钮布局器太高了,而且按钮也拉得太宽。
(5)下面按钮部分微调:
我们选中 "提交" 按钮,把它的水平策略调整为 Fixed;
我们选中 "取消" 按钮,把它的水平策略也调整为 Fixed。得到下图效果:
然后我们点击主窗体的空白区域,选中主窗体本身,可以看到主布局器属性栏,把垂直布局器的 layoutStretch 伸展因子设置为:3,1这样让网格布局器占据垂直方向大部分区域:
现在下面两个按钮的布局问题算是解决了。上面网格布局器还是有问题,第二行的单选按钮布局器占据了太大空间,而不是我们希望的每行各占三分之一。
(6)网格布局器细节调整
对于上面的网格布局器,我们希望三行各占三分之一,最直观的想法是设置垂直方向的伸展因子。
因为界面里多个布局器嵌套,选中某个子布局器的操作其实不好实现。
我们直接在右上角的布局树,点击 gridLayout 条目,就可以很快捷地选中该网格布局器,然后把垂直方向的行伸展因子 layoutRowStretch 设置为1,1,1得到下图的效果:
这里看到设置三个行的伸展因子并没有效果,因为右边一列的单行编辑控件和组合框在垂直方向都是固定高度的,而中间的单选按钮布局器默认是尽量拉伸,所以即使设置了 行的伸展因子也没有用。
如果我们修改右边单行编辑控件和组合框的垂直伸展策略为主动扩张,那么这两个控件会被拉得很高,但是只显示一行文本,从显示上看会非常别扭。
我们的视野不要局限在右边一列,可以从左边一列的标签控件来打主意。
标签在垂直方向默认都是 Preferred 被动拉伸的策略,如果我们把左边一列三个标签的垂直策略全部设置成 Expanding,让三个标签都去主动占据垂直方向的空间,那么会有惊喜出现:
这样就得到了我们想要的布局效果了。
我们保存界面文件,然后生成运行例子,看看运行效果:
最后顺便提一下,如果读者希望标签的文本在水平方向居中,是可以设置标签控件的 alignment 属性的,调整为水平居中 AlignHCenter 即可。修改界面后要点击 QtCreator 菜单【构建-->重新构建 "项目名"】,然后就可以看到文本水平居中的标签。读者可以自行测试一下,这里不再截图示范了。
本小节的例子同时示范了伸展策略和伸展因子的运用,另外,布局时不但可以从我们需要调整的列考虑,还可以从相邻的列角度考虑问题,比如要调整第二列的输入控件布 局,其实可以调整第一列标签控件的垂直策略来实现我们想要的效果。思维可以开阔一些,旁敲侧击有时候也是好方法。
|