精通Qt4编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 基础窗口部件——QWidget

在前面的章节中,学习了直接继承Qt对话框类QDialog实现自定义的对话框,并通过手写代码实现了子窗口部件的添加和排布。从本章开始,将学习使用Qt的GUI界面设计器(Qt Designer)进行界面的绘制和布局,学习如何在应用程序中加载和使用Qt设计器绘制的窗口部件;学习Qt的基础窗口部件——QWidget的基本特性和功能,并深入地介绍Qt元对象系统、属性系统和对象树机制。

3.1 Qt设计器绘制窗口部件

Qt设计器是Qt GUI编程语言一系列工具中的一个,该工具提供了Qt基本的可绘制窗口部件,比如QWidget、QLabel、QPushButton、QVBoxLayout等等。在设计器中通过鼠标直接拖放这些窗口部件,能够高效、快速地实现GUI界面的设计,界面直观形象,所见所得。

本节将从绘制一个自定义的QWidget窗口部件来学习Qt设计器的使用,同时也将了解Qt的资源文件(*.qrc)、ui文件(*.ui)以及相应的编译工具——资源编译器(Resource Compiler, rcc)和用户界面编译器(User Interface Compiler, uic)。

3.1.1 Qt设计器基础

首先,将Qt设计器打开。在控制台执行命令

        designer&

其中,“&”表示将在后台运行Qt设计器,这样就可以继续使用该控制台运行其他的命令。进入Qt设计器的默认启动界面,如图3-1所示。

在“New Form”对话框中,可以选择要设计的顶层窗口部件类型(顶层窗口部件是其他子窗口部件的载体):

● Dialog with Buttons Bottom,底部具有“确定”和“取消”按钮的对话框;

● Dialog with Buttons Right,右侧具有“确定”和“取消”按钮的对话框;

● Main Window,应用程序主窗口界面;

● Widget,Qt的基础窗口部件QWidget。

“New Form”对话框的右侧是窗口部件类型(template/forms)的预览区。

进入Qt设计器主界面后,就可以使用Qt的标准窗口部件进行GUI可视化设计了。在Qt设计器的左侧“Widget Box”栏列出了经常使用的Qt标准窗口部件,单击鼠标左键选中相应的窗口部件图标,可以将它们直接拖放在顶层窗口部件的界面上。同时,也可以将自己设计的窗口部件组合(通过使用布局管理器对Qt标准窗口部件进行布局和组合)或放置了其他窗口部件的Qt容器类(见“Widget Box”栏的“Containers”组)直接拖放到“Widget Box”栏中,Qt设计器会自动在“Widget Box”栏中生成“Scratchpad”组,并生成新的自定义的窗口部件。以后可以像使用Qt提供的标准窗口部件一样使用自己创建的窗口部件。

图3-1 Qt设计器初始界面

图3-2 Qt设计器中的部件

选中Qt设计器Tools菜单中的全部复选框,在Qt设计器的右侧可以看到设计器提供的一些编辑工具,如图3-3所示。

对象监视器(Object Inspector):列出了界面中的所有窗口部件,以及各窗口部件的父子关系和包容关系。

属性编辑器(Property Editor):列出了窗口部件可编辑的属性。

动作编辑器(Action Eidtor):列出了为窗口部件设计的QAction动作,通过“添加”或“删除”按钮可以新建一个可命名的QAction动作或删除指定的QAction动作。

信号/槽编辑器(Signal/Slot Editor):列出了在Qt设计器中关联的信号和槽,通过双击列中的对象或信号/槽,可以进行对象和信号/槽的选择。

资源编辑器(Resource Editor):列出了程序使用的资源文件(*.qrc)以及相应的资源,在该编辑器中可以创建或删除资源文件,也可以添加或取消资源。

图3-3 Qt设计器提供的编辑器

此外,通过Qt设计器的“Edit”菜单,可以打开Qt设计器的4种GUI窗口部件编辑模式:

Widget编辑模式(Widget Editing Mode):可以在Qt设计器中添加GUI窗口部件,以及修改它们的属性和外观。

信号和槽编辑模式(Signals and Slots Editing Mode):可以在Qt设计器中的窗口部件上关联Qt已经定义好的信号和槽。

伙伴编辑模式(Buddy Editing Mode):可以在Qt设计器中的窗口部件上建立QLabel标签和其他窗口部件的伙伴关系。

Tab编辑模式(Tab Order Editing Mode):可以在Qt设计器中的窗口部件上设置Tab键在窗口部件上的焦点顺序。

扩展阅读

QLabel标签和伙伴(buddy)窗口部件

一个标签QLabel和一个窗口部件具有伙伴关系,即指当用户激活标签的快捷键时,鼠标/键盘的焦点将会转移到它的伙伴窗口部件上。只有QLabel标签对象才可以有伙伴窗口部件,也只有该QLabel对象具有快捷键(在显示文本的某个字符前面添加一个前缀“&”,就可以定义快捷键)时,

伙伴关系才有效。例如,

        QLineEdit*  priceLineEdit   = new QLineEdit(this);
        QLabel*     priceLabel  = new QLabel("&Price", this);
        priceLabel->setBuddy(priceLineEdit);

代码定义了priceLabel标签的快捷键为“Alt + P”,并将行编辑框priceLineEdit设为它的伙伴窗口部件。所以当用户按键“Alt+P”时,焦点将会跳至行编辑框priceLineEdit中。

Qt设计器提供了伙伴编辑模式,可以通过鼠标拖放操作快捷地建立标签QLabel和其他窗口部件的伙伴关系。

对于Qt设计器提供的上述工具和编辑模式,将在下一节学习。在此,只对Qt资源系统(Qt Resource System)略加介绍。

Qt资源系统是平台无关的,它以二进制代码保存可执行应用程序运行时使用的文件(如图标文件)。在编译Qt应用程序时,Qt资源编译器rcc会对Qt资源文件进行编译,并生成相应的二进制代码文件(最后由连接程序将该二进制代码文件连接为应用程序的一部分),而该资源文件一旦通过编译就不能再改变,除非重新编译Qt应用程序。因此,需要运行时改变的文件是不能放置在Qt的资源系统中的(例如,在程序运行期间需要编辑修改的XML文件)。

Qt资源文件*.qrc是XML格式的文本文件,它记录了Qt应用程序使用的资源。资源文件及其记录的资源是应用程序源码的一部分,一个应用程序可以有多个资源文件。注意,资源文件*.qrc中记录资源的路径是资源相对于资源文件*.qrc的位置。

对于上述目录结构描述的资源,其资源文件hello.qrc的内容为:

        <!DOCTYPE RCC>
        <RCC version="1.0">
        <qresource>
        <file>lines/dotline.png</file>
        <file>lines/dashdotline.png</file>
        <file>lines/solidline.png</file>
        <file>lines/dashline.png</file>
        </qresource>
        </RCC>

这是一个XML格式的文本文件。

第1行XML语句声明了XML文档的类型为RCC,即Qt资源。

<RCC version="1.0">引入了XML文档的第一个元素RCC,并声明了RCC的版本为1.0。

元素<qresource>指示其子元素是资源元素。四行<file>元素记录了应用程序使用的资源,这些资源放置在lines子目录下(如图3-4所示)。

使用资源系统之前,必须将定义的资源文件 *.qrc引入到qmake工程文件中。对于hello.qrc则必须在qmake工程文件中加入下列一行:

        RESOURCES       += hello.qrc

图3-4 资源文件及其资源

它表示应用程序用到了资源文件hello.qrc,并告知qmake工具。

接下来,就可以在应用程序启动的时候通过使用Q_INIT_RESOURCE()函数进行资源的初始化。

        int main(int argc, char *argv[])
        {
        QApplication app(argc, argv);
        Q_INIT_RESOURCE(hello);
        ...
        return app.exec();
        }

宏Q_INIT_RESOURCE() 初始化hello.qrc记录的资源,“hello”是资源文件的名称。之后,就可以在应用程序中引用资源了(应用程序的资源将在应用程序启动的时候自动加载)。例如,创建一个带有实线图标的动作对象:

        QAction* act = new QAction(QIcon(“:/lines/solidline.png”),tr(“实线”), this);

在上述创建QAction动作对象的时候,使用了QAction(const QIcon& icon, const QString&text, QObject* parent)构造函数,它的形参icon和text都是对常量对象的引用,因此可以传入不具名的QIcon(”:/lines/solidline.png”) 对象作为实参和临时QString对象tr(”实线”)作为实参。而对于非常量对象的引用,不具名的对象、临时对象和具体数值是不能够作为实参的。例如,如果其构造函数定义为QAction(QIcon& icon, QString& text, QObject* parent),那么上述QAction对象的一个合法的构造方法是:

        QIcon icon(”:/lines/solidline.png”);
        QString str(tr(”实线”));
        QAction* act = new QAction(icon, str, this);

对于C++内嵌的数值性数据类型亦是如此。不过,很少有程序员将一个数值作为引用来传递,毕竟内嵌的简单数据类型的传值参数的开销是非常小的。

在定义好应用程序的资源/资源文件以及在程序中添加了初始化和加载资源的代码后,运行qmake生成Makefile文件,接着运行make命令编译/连接应用程序,gcc编译器自动调用Qt的rcc资源编译器编译hello.qrc及其资源并生成C++源文件qrc_hello.cpp,最后gcc编译qrc_hello.cpp文件并同其他的目标代码连接成可执行的应用程序,如图3-5所示。

图3-5 Qt资源的编译/链接

此外,应用程序也可以使用外部的二进制资源(External Binary Resources),外部二进制资源不作为应用程序的二进制代码的一部分。这种方式脱离于应用程序,可以在需要的时候进行动态加载。

要使用外部二进制资源,首先根据资源文件将资源编译成二进制的资源数据(文件的扩展名通常为.rcc)。例如,上述的hello.qrc资源文件及其资源,将编译为二进制文件hello.rcc。在控制台执行下列命令:

        rcc -binary hello.qrc -o hello.rcc

然后,在应用程序中注册资源(假设二进制资源文件位于/home/lcf/book/rcc/目录下):

        QResource::registerResource(“/home/lcf/mybooks/rcc/hello.rcc”);

接着就可以在应用程序中使用了,使用的方法同前面讲过的使用方法相同。

如果资源文件*.qrc中记录的资源不存在,在编译的时候则会报错。而如果在应用程序中引用有错误时,编译不会报错,但在运行应用程序时会导致一些问题,例如无法显示图标等。

3.1.2 绘制窗口部件

上一节,讲到了Qt设计器的一些基础知识。现在,使用Qt设计器绘制一个实现查找文件功能的GUI窗口部件。

图3-6 查找文件界面

进入Qt设计器界面后,在“New Form”对话框的“template/forms”选项卡中选择“Widget”,单击“create”按钮。Qt设计器将生成一个顶层的窗口部件,并以网格的方式显示该窗口部件。

将窗口部件的对象名字(objectName)属性修改为“FindFileForm”(Qt的uic编辑器将根据顶层窗口部件的对象名字生成该界面的窗口部件类,将在下面讲到)。将窗口拉伸到一个合适的大小,或者通过设置窗口部件的“geometry”属性值改变窗口的大小。接下来,添加必要的子窗口部件,如图3-6所示。

按如下步骤添加各窗口部件:

添加“名为”、“包含文本”、“查找位置”QLabel标签以及相关的编辑框(由上到下依次为QComboBox、QLineEdit和QComboBox),添加一个“浏览...”QPushButton按钮;设置它们的属性(如表3-1所示,通过属性编辑器设置),调整大小和位置,然后选中所有这些窗口部件(按shift键的同时用鼠标左键点选窗口部件),在选中的窗口部件上单击鼠标右键,选择上下文菜单“layout”的菜单命令“Layout in a Grid”,把它们放置在一个网格布局管理器中(或直接单击布局管理器工具栏中的“网格布局管理器”工具按钮,如图3-7所示)。

表3-1 窗口部件属性表(按从左到右、从上至下顺序,不包括布局管理器)

图3-7 布局管理器工具栏

排布后的各窗口部件如图3-8所示。

图3-8 使用网格布局管理器组织输入窗口部件

添加两个QCheckBox复选框窗口部件:“区分大小写”复选框sensitiveCheckBox(对象名字)和“包含子文件夹”复选框subfolderCheckBox,并放置在一个水平布局管理器QHBoxLayout中,如图3-9所示。

图3-9 使用水平布局管理器组织复选框窗口部件

为了界面的美观,使用了一些Sapcer窗口部件(蓝色的像弹簧状的),选中这些Spacer可以在属性编辑器中设置它的sizeType属性。在Qt设计器中将Spacer加入到一个布局管理器,有两种办法:

● 选中各子窗口部件的同时也选中Spacer,把它们件加入到布局管理器中;

● 将各子窗口部件加入一个布局管理器后,将Spacer拖放到该布局管理器的适当位置。

设置Spacer窗口部件的属性:

● 左侧Spacer的sizeType属性为QSizePolicy::Fixed(其他采用默认值);

● 右侧Spacer的属性全部采用默认值。

添加一个容器QFrame子窗口部件(属性采用默认值),并将步骤生成的窗口部件组合依次拖放到该QFrame子窗口部件中,并设置它的布局管理器为垂直布局管理器,效果如图3-10所示。

图3-10 使用QFrame容器重新组织窗口部件

添加三个QPushButton窗口部件:“查找”按钮findPushBtn、“停止”按钮stopPushBtn和“关闭”按钮closePushBtn,并放置在一个水平布局管理器中;然后放入4个Spacer窗口部件,属性采用默认值,如图3-11所示。

图3-11 使用水平布局管理器组织按钮窗口部件

添加“查找结果”QLabel标签和QTableWidget表窗口部件resultTableWidget,在表窗口部件中单击鼠标右键,在弹出的上下文菜单中选择“Edit Items...”命令,在出现的“Edit Table Widget”对话框中添加表列:名称、大小、修改时间和权限,如图3-12所示。

图3-12 编辑表格窗口部件

添加“状态”标签statusLabel和“查找结果”标签resultLabel,设置它们的sziePolicy属性(如表3-2所示),并把它们放置在一个水平布局管理器中。

表3-2 窗口部件属性表

排布好的“状态“和”“查找结果”标签如图3-13所示。

图3-13 使用水平布局管理器组织状态显示标签

最后,选中顶层窗口部件(即最初建立的QWidget窗口部件),然后将整个窗口部件放置在一个垂直布局管理器中(如图3-6所示)。

现在使用Qt资源文件为应用程序用户界面的标题栏加一个标题和图标。

选择“Tools”菜单的“Resource Eidtor”命令,打开资源编辑器。

在“Current Resource”下拉框中选择“New...”选项(如图3-14所示),打开“新建资源文件”对话框。

在“新建资源文件”对话框中,定位到资源文件放置的目录(本例为chapter03/findfile/),输入资源文件的名字findfile.qrc,保存之,如图3-15所示。

图3-14 资源编辑器

图3-15 新建资源文件

在“资源编辑器”对话框中单击“+”操作,添加一个前缀,可以根据自己的需要修改,在此采用默认值,如图3-16所示。

单击“Add Files...”按钮,打开“Open File”对话框,选择需要添加的图标资源,并单击“打开”按钮,如图3-17所示。

图3-16 编辑资源文件

图3-17 为资源文件打开一个资源

现在findfile.png图标文件就加入到了资源文件findfile.qrc中,其添加资源后的状态如图3-18所示。

在对象监视器中选择顶层窗口部件FindFileForm对象,并在属性编辑器中选择“windowTitle”属性,将“Form”修改为“查找文件”;选择“ windowIcon ”属性选项,并单击下拉框右侧的“打开”按钮,如图3-19所示。

图3-18 添加资源后的资源文件状态

图3-19 设置窗口部件的窗口图标

在“Find icon”对话框中有两个单选项:选项“Specify image file”表示通过绝对路径来定位图标文件;选项“Specify resource”表示通过资源文件来定位资源。笔者推荐使用选项“Specify resource”,因为采用绝对路径来定位图标文件,往往会导致应用程序在安装后系统无法找到该图标文件。选中“Specify resource”,这时候“Current Resource”列表中会列出已经创建的资源文件以及在文件中添加的资源。选中刚刚添加的图标资源findfile.png,单击“确定”按钮。如图3-20所示。

图3-20 指定图标资源

在Qt设计器中预览GUI用户界面(可以通过快捷键“Ctrl+R”激活预览命令),可以看到图标已经添加到顶层窗口部件“FindFileForm”用户界面的标题栏上了。

绘制好GUI用户界面后,保存为“findfileform.ui”文件(该ui文件在源代码中的路径为“chapter03/findfile/ findfileform.ui”),该文件是XML格式的文本文档。一个比较小的ui文件的内容如下所示。

        <ui version="4.0" >
            <class>Dialog</class>
            <widget class="QDialog" name="Dialog" >
                <property name="geometry" >
                        <rect>
                            <x>0</x>
                            <y>0</y>
                            <width>400</width>
                            <height>300</height>
                        </rect>
                </property>
                <property name="windowTitle" >
                        <string>Dialog</string>
                </property>
                <widget class="QPushButton" name="pushButton" >
                        <property name="geometry" >
                            <rect>
                                    ……
                            </rect>
                        </property>
                        ……
                </widget>
                ……
            </widget>
            <resources/>
            <connections/>
        </ui>

有时候,在Qt设计器中修改窗口部件的类型和属性是比较麻烦的,比如修改顶层窗口部件的类型(例如,将QDialog改为QWidget)。而直接修改ui文件很容易就做到了(在Qt设计器中无法做到,必须要重新建立一个新的QDialog顶层窗口部件)。有兴趣的读者可以对照Qt设计器中的界面和属性,对Qt的ui文件仔细分析一下,在此不再赘述。

在Qt设计器中绘制好GUI界面后,就可以利用Qt提供的qmake工具和uic编译工具将ui文件编译生成C++源文件。该源文件默认的命名规则是:

        ui_<ui文件名>.h

对于绘制的GUI用户界面文件findfileform.ui,uic编译工具将生成名为“ui_findfileform.h” 的C++代码文件。通过在qmake工程文件中配置FORMS选项,可以让uic编辑工具自动完成上述过程(在下一节进行详细描述)。

现在在控制台上执行uic命令,看一下生成的ui_findfileform.h文件 (也可以生成自己喜欢的名字或直接输出在控制台):

        uic -o ui_findfileform.h findfileform.ui

或者

        uic  findfileform.ui(直接输出到控制台)

生成的C++源文件的内容如下所示(由于文件过大,部分省略)。

        #ifndef UI_FINDFILEFORM_H
        #define UI_FINDFILEFORM_H

        #include <QtCore/QVariant>
        #include <QtGui/QAction>
        #include <QtGui/QApplication>
        ……
        class Ui_FindFileForm
        {
            public:
            QVBoxLayout *vboxLayout;
            QGridLayout *gridLayout;
            QLineEdit *txtLineEdit;
               QPushButton *browsePushBtn;
               ……
            void setupUi(QWidget *FindFileForm)
            {
               FindFileForm->setObjectName(QString::fromUtf8("FindFileForm"));
               vboxLayout = new QVBoxLayout(FindFileForm);
               vboxLayout->setSpacing(6);
               vboxLayout->setMargin(9);
              vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));
               gridLayout = new QGridLayout();
               gridLayout->setSpacing(6);
                gridLayout->setMargin(0);
                gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
               txtLineEdit = new QLineEdit(FindFileForm);
               txtLineEdit->setObjectName(QString::fromUtf8("txtLineEdit"));

               gridLayout->addWidget(txtLineEdit, 1, 1, 1, 2);
               ……
               retranslateUi(FindFileForm);

               QSize size(438, 387);
               size = size.expandedTo(FindFileForm->minimumSizeHint());
               FindFileForm->resize(size);
               QMetaObject::connectSlotsByName(FindFileForm);
        } // setupUi

        void retranslateUi(QWidget *FindFileForm)
        {
            FindFileForm->setWindowTitle(
                            QApplication::translate("FindFileForm",
                            "Form", 0, QApplication::UnicodeUTF8));
            ……
            Q_UNUSED();
        } // retranslateUi
      };

        namespace Ui {
           class FindFileForm: public Ui_FindFileForm {};
        } // namespace Ui

        #endif // UI_FINDFILEFORM_H

该ui_findfileform.h文件定义了一个POD(Plain Old Data)类Ui_FindFileForm,该类的名字来自Qt设计器绘制的Qt GUI用户界面的顶层窗口部件(“objectName”属性为“FindFileForm”的窗口部件)的对象名。在类Ui_FindFileForm定义的公共区,包含了在Qt设计器中添加的所有子窗口部件的指针,并定义了几个内联函数。其中,最重要的是公共函数setupUi(),该函数对传入的Qt标准窗口部件QWidget对象的标题、大小等进行设置。它的参数类型“QWidget *”决定了使用的顶层窗口部件(或主容器)的类型是QWidget,如果在Qt设计器中使用QMainWindow作为顶层窗口部件,那么setupUi()的参数类型将是“QMainWindow *”。

静态函数QMetaObject::connectSlotsByName(QObject * object)将递归地搜寻传入的Qt对象object的所有子对象,并把所有匹配的子对象的信号关联到object对象的符合下列规则的槽函数(将在本章的3.3节详细描述):

        void on_<窗口部件名称>_<信号名称>(<信号参数>)

setObjectName() 函数设置对象的名称,以便能通过QObject::findChild() 以及QObject::objectName()函数获取这些对象的引用。

注意,uic工具生成的Ui_FindFileForm类并不是一个QWidget类,也不是一个QObject对象,它的作用仅仅是构建和部署Qt设计器中绘制的窗口部件。