11.4 堆栈布局
堆栈布局也是布局管理器的一种。它把一系列的窗口部件排列成类似堆栈的样子,但 每次只能有一个窗口部件是当前的可见窗口。
11.4.1 使用方法
QStackedLayout 类被用来创建堆栈布局的实例。使用堆栈布局创建出来的应用程序的 样子与使用 QTabWidget 创建的效果有些相像。也可以使用 QStackedWidget 类来创建一个 应用程序界面,和使用 QStackedLayout 效果差不多,目前在最新的 4.5.2 版 Qt 中,我们如果使用 Qt Designer 创建用户界面,那么通常会使用 QStackedWidget,这点在后面还会讲到。
实际上,你可以把堆栈布局看成是由一系列的子窗口部件组成的,它们都是一些 “页 面”(pages)。
在使用堆栈布局时,首先应包含其头文件:
#include <QStackedLayout>
下面是手写代码创建一个堆栈布局的实例:
QWidget *firstPageWidget = new QWidget;
QWidget *secondPageWidget = new QWidget;
QWidget *thirdPageWidget = new QWidget;
QStackedLayout *stackedLayout = new QStackedLayout;
stackedLayout->addWidget(firstPageWidget);
stackedLayout->addWidget(secondPageWidget);
stackedLayout->addWidget(thirdPageWidget);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(stackedLayout);
setLayout(mainLayout);
创建栈布局的大致步骤如下:
第 1 步,创建布局内的窗口部件,也即是各个页面。
第 2 步,创建栈布局的实例,也就是创建一个 QStackedLayout 类的实例,并将第 1 步 创建的窗口部件加入到布局之中。
向布局内加入窗口部件的方法可以使用 addWidget(),也可以使用 insertWidget(), 前者将把窗口部件加入到子窗口索引的最后,而后者则可以指定在索引中的位置加入窗口部 件。可以根据实际需要选用。
第 3 步,调用 QWidget::setLayout()函数将布局安装到窗体上。 11.4.2 如何索引窗口部件
由于 QStackedLayout 类并没有提供位于其内部的窗口部件(即上文提到的 “页面”) 的索引,所以我们在使用堆栈布局时,通常需要使用组合框类( QComboBox)或者列表部件 类(QListWidget)的实例来存储这些子窗口的索引,继而实现切换堆栈布局中子窗口的目 的。下面是一个使用 QComboBox 类的实例:
QComboBox *pageComboBox = new QComboBox;
pageComboBox->addItem(tr("Page 1"));
pageComboBox->addItem(tr("Page 2"));
pageComboBox->addItem(tr("Page 3"));
connect(pageComboBox, SIGNAL(activated(int)),
stackedLayout, SLOT(setCurrentIndex(int)));
在上述代码中,通过 addItem()方法为 QComboBox 的实例依次加入堆栈布局的索引,这样当调用 connect()函数显式的连接 pageComboBox 的 activated()信号和 stackedLayout 的 setCurrentIndex()槽时,这些索引就会被加入到内部的一个链表中,这样就可以方便的 查找布局内的窗口部件了。
要获得栈布局内的窗口部件的数量,可以使用 count()方法。 那么我们如何找到位于堆栈布局内的任意一个子窗口呢,可以使用堆栈窗口的 widget() 方法返回当前指定位置索引的子窗口,它的原型如下:
QWidget * QStackedLayout::widget ( int index ) const
它将返回由 index 指定的位置的子窗口,当返回值为 0 时,则表示在 index 指定的位置处并没有任何子窗口。
作为上述的一个特例,要找到当前的活动子窗口,可以先找到当前它对应的 index,方 法是使用 currentIndex()方法,它的原型如下:
int currentIndex () const
如果要取得某一个窗口部件在布局中的索引,可以使用 indexof()方法,其原型如下:
int QLayout::indexOf ( QWidget * widget ) const [virtual]
该方法将 widget 在布局内的索引返回,如果返回值为-1,则表示没有找到这个窗口部件。
小贴士:实际上堆栈布局有一个重要的属性 currentIndex : int,是一个 int 常量,它对 应当前活动子窗口的索引,获得该属性值的方法即是调用 currentIndex()方法。如果它的 值为-1,则表示没有当前活动子窗口,也就是没有子窗口在堆栈布局内。
要想找到当前的活动子窗口,可以使用 currentWidget()方法,它的原型如下:
QWidget * QStackedLayout::currentWidget () const
它将返回堆栈布局内当前的活动子窗口,如果返回值为 0,则表示没有当前的 堆栈布局内没有子窗口,由此这个函数也可以被用来判断当前布局内是否存在窗口部件。
要想指定一个位于堆栈布局内部的子窗口为当前的活动窗口,可以使用 setCurrentWidget()方法,它的原型如下:
void QStackedLayout::setCurrentWidget ( QWidget * widget ) [slot]
它将把你指定的 widget 作为当前的活动子窗口,前提是这个子窗口必须已经位于堆栈 布局内部。
此外,一旦堆栈布局内当前的窗口部件产生了变化或者被移除,布局就会发 出 currentChanged() 以及 widgetRemoved() 信号。
11.4.3 实例-堆栈窗体
本实例实现一个堆栈窗体的使用,实现的效果如图 11-23 所示。当用户选择左侧列表 框中不同的选项时,右侧则对应显示所选的窗体。
图 11-23 堆栈窗体实例
为了使读者朋友熟练掌握使用方法,这里将用两种方法为大家分别实现,一是完全手 写代码的方法,源代码见实例 stack。二是使用 Qt Designer 设计界面,然后在 Qt Creator 中创建工程辅以部分手写代码的方法,源代码见 stackDlg。
1.完全手写代码
实现头文件 stackdlg.h。
class StackDlg : public QDialog
{
Q_OBJECT
public:
StackDlg(QWidget *parent = 0, Qt::WindowFlags f1 = 0);
QLabel *label1;
QLabel *label2;
QLabel *label3;
QListWidget *listWidget;
QStackedWidget *stackWidget;
};
在头文件中声明一个对话框类,它继承自 QDialog,然后声明所用到的窗口部件。 实现源文件 stackdlg.cpp。
StackDlg::StackDlg(QWidget *parent, Qt::WindowFlags f1)
: QDialog(parent,f1)
{
setWindowTitle(tr("Stacked Widgets"));
listWidget = new QListWidget(this);
listWidget->insertItem(0,tr("The Window 1"));
listWidget->insertItem(1,tr("The Window 2"));
listWidget->insertItem(2,tr("The Window 3"));
label1 = new QLabel(tr("It is Window 1 !"));
label2 = new QLabel(tr("It is Window 2 !"));
label3 = new QLabel(tr("It is Window 3 !"));
stackWidget = new QStackedWidget(this);
stackWidget->addWidget(label1);
stackWidget->addWidget(label2);
stackWidget->addWidget(label3);
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->setMargin(5);
mainLayout->setSpacing(5);
mainLayout->addWidget(listWidget);
mainLayout->addWidget(stackWidget,0,Qt::AlignHCenter);
mainLayout->setStretchFactor(listWidget,1);
mainLayout->setStretchFactor(stackWidget,3);
connect(listWidget,SIGNAL(currentRowChanged(int)),stackWidget,SLOT(setCurrentIndex(int)));
}
第 1 行设置应用程序的标题,注意要使用 tr()函数。
第 2-5 行创建乐一个 QListWidget 窗口部件的实例,并在其中插入 3 个条目,当我们
在 3 个条目之间切换时,右面的堆栈窗口将会对应变化 。
第 6-8 行依次创建了 3 个 QLabel 窗口部件的实例,作为右面的堆栈窗口中对应显示的 3 个窗体。
第 9 行创建了一个 QStackWidget 堆栈窗体的实例。
第 10-12 行调用 addWidget()方法把前面创建的 3 个 QLabel 窗体部件的实例依次插入 到堆栈窗中。
第 13 行设置了一个顶层(Top Level)布局,它是一个 QHBoxLayout 实例。 第 14 行调用 setMargin()函数设置布局距离窗体边界的尺寸。
第 15 行调用 setSpacing()设置了在该布局中的窗体部件之间的距离。该函数的原型如 下:
void setSpacing ( int )
小贴士:QLayout 有两个非常重要的属性:
- sizeConstraint : SizeConstraint 该属性定义了布局的伸缩模式。
- spacing : int 该属性定义了在一个布局内部的窗口部件之间的距离,也就是间距。
如果没有明确的设置该属性值,那么该布局将继承它的父布局的设置,或者该布局中的 窗口部件将继承它们的父窗口部件的设置。
再次提醒,当使用 QGridLayout 或者 QFormLayout 布局时,很可能要分别设置不同的水 平和垂直方向的间距,这时就可以使用 setHorizontalSpacing() 、 setVerticalSpacing()这两个方法。当调用了这两个方法设置了水平和垂直间距后,再调用 spacing()获取该属性,这时它的返回值就是-1 了。
第 20 行连接 QListWidget 的 currentRowChanged()信号与堆栈窗的 setCurrentIndex() 槽,实现按选择显示窗体。此处的堆栈窗体 index 按插入的顺序从 0 起依次排序,与 QListWidget 的条目排序相一致。
2.Qt Designer 结合 Qt Creator 实现
本实例分析了堆栈窗的基本使用方法。在实际应用中,堆栈窗口多与列表框 QListWidget 或者下拉列表框 QComboBox 配合使用。
这种方法是前面第 5 章所讲述的 Qt 编程的 3 种基本方法之一,在此引领大家温习一 遍。对于.ui 文件的使用我们选用单继承法,如果对此法的使用还有不熟悉的地方,请回到 第 7 章复习。
项目的实现步骤如下:
第 1 步,创建一个新的项目
打开 Qt Designer,创建一个名为 stackDesigner 的新的项目,项目类型为用以存放项目文件。
第 2 步,创建程序界面文件(.ui 文件)
I. 启动 Qt Designer,在其中创建本程序的界面,模板的类型可以使 Widget 或者是 Dialog 等。
II. 从窗口部件盒内依次拖拉出 1 个 ListWidget、1 个 Horizontal Spacer 和 1 个 Stacked Widget,把它们摆放在大致的位置即可,它们构成了顶级布局的要素。
III. 然后在 ListWidget 的界面上双击鼠标左键,即可在弹出的如图 11-24 所示的【编辑 列表窗口部件】对话框中为列表添加项目,依次添加 3 个:window1、window2 和 window3。
图 11-24 为列表窗口部件添加项目
IV. 接下来,为堆栈窗口部件添加子窗口部件,也就是页。初始情况下,堆栈窗口已经 为我们内置了两个页,我们从窗口部件盒中拖出 2 个 Label,分别放入两个页中。
注意,切换这两个页的方式是用鼠标点击堆栈窗口右上角的那两个黑色的三角形的导 航按钮,左边的那个是向前翻页,右边的那个是向后翻页。
V. 然后我们需要为堆栈窗口部件添加第 3 个页,方法先切换到第 2 个页,然后用鼠标 右键点击那两个导航按钮之一,在弹出的上下文菜单中依次选择 【插入页】→【在当前页 之后】,就完成了第 3 个页的插入。然后仿照上面的步骤,向其中加入 1 个 Label。
VI. 这之后,再在每个页面上 Label 的左右两侧各放置一个 Horizontal Spacer。
VII. 接下来我们为窗口部件设置属性,为简单起见,都取默认属性,而 3 个 Label 窗口 部件的 text 属性设置为 It is Window 1、It is Window 2 和 It is Window3。
必须要设置的是 Stacked Widget 的第 3 个页的 objectName 属性,这是 Qt Designer 的一个 bug,在目前的 4.5.2 版 Qt 里,你自己增加的页面的 objectName 属性将被 Qt Designer 命名为类似“页”的乱码,这将导致后面 qmake 运行时的错误。把它手动修改为 page_3。
VIII. 然后我们从里到外,依次为窗口部件布置布局。
布置好的样子如图 11-25 所示,你可以清楚的看到各个布局的情况,在此不再赘述。
图 11-25 在对象查看器中看到的布局情况
第 3 步,向项目中引入该界面文件。
把这个文件命名为 stack Designer,保存在我们的项目目录下。在项目上点击鼠标右 键,在上下文菜单中选择【Add Existing Files...】,把该.ui 文件加入到工程中。
第 4 步,运行 qmake,以生成 ui_stackDesigner.h 头文件。
这时,先运行一下 qmake,它将调用 moc,以生成需要的 ui_stackDesigner.h 头文件。
第 5 步,自定义一个界面类,采用单继承方法使用界面文件。
首先新建一个名为 stackDesignerDlg.h 的头文件,其内容如下:
#ifndef STACKDESIGNERDLG_H
#define STACKDESIGNERDLG_H
#include <QtGui>
#include "ui_stackDesigner.h"
class StackDesignerDlg : public QDialog
{
Q_OBJECT
public:
StackDesignerDlg(QWidget *parent = 0, Qt::WindowFlags f1 = 0);
private:
Ui::Form ui;
};
#endif // STACKDESIGNERDLG_H
在其中定义了该类公有单继承自 QDialog,以及它的构造函数。最重要的是声明了一个私有变量 ui,它是界面原生类的对象,这是单继承法最明显的特征。再建立该类的实现文件,其内容如下:
#include "stackDesignerDlg.h"
StackDesignerDlg::StackDesignerDlg(QWidget *parent, Qt::WindowFlags f1)
: QDialog(parent,f1)
{
setWindowTitle(tr("Stacked Widgets"));
ui.setupUi(this);
connect(ui.listWidget,SIGNAL(currentRowChanged(int)),ui.stackedWidget,SLOT(setCurrentIndex(int)));
}
为简单起见,这里主要实现了其构造函数。
首先加入了类头文件声明。然后设置程序的标题,调用 setupUi()方法来完成界面布局 的初始化。
接下来把 listWidget 的 currentRowChanged()信号和 stackedWidget 的 setCurrentIndex()槽关联起来。
第 6 步,书写主程序文件。
完成了界面类的定义后,最后新建一个 main.cpp 文件,其内容如下:
#include <QApplication>
#include "stackDesignerDlg.h"
int main( int argc, char * argv[] )
{
QApplication a( argc, argv );
StackDesignerDlg stack;
stack.show();
return a.exec();
}
这段代码中定义一个我们刚才定义的界面类的实例,然后把它显示出来。
第 7 步,编译运行程序。
我们采用快捷键,依次按下 Ctrl+B 和 Ctrl+R 编译运行程序,其效果如图 11-26 所 示,与手写代码无异。
图 11-26 程序运行效果
需要注意的是,目前的 Qt Creator 并没有把 Qt Designer 的全部功能集成进来,比如 我们无法在其中预览 Qt Designer 生成的界面效果,因而笔者建议大家最好还是直接在 Qt Designer 中设计界面,然后使用 Qt Creator 完成项目的构建。
总而言之,这两种方法都是常用的,且各有长处。手写代码的方式似乎更加简洁,而 Qt Designer 结合 Qt Creator 的方式能使编程过程简化,且可以直观的验证你的布局情 况。读者朋友可以根据自己的喜好掌握。而笔者建议最好是将手写代码方式弄通,熟练之后 再使用 IDE 的方式,这样也能更好的理解 Qt 编程的精髓。