2.2 案例2 整理一下目录吧

本案例对应的源代码目录:src/chapter02/ks02_02。程序运行效果见图2-11。

图2-11 案例2运行效果

使用Qt进行开发的目的之一是开发界面类应用。本节将介绍用Qt开发界面类应用的基本步骤,并介绍如何通过修改pro文件的配置使源代码目录保持整洁。

开发界面类项目的过程大概分为四步。

(1)使用Designer绘制对话框资源文件(ui)并保存。

(2)编写界面ui对应的类CDialog。

(3)将相关文件添加到pro。

(4)使用CDialog定义对象。

下面分步骤讲解。

1.使用Designer绘制对话框资源文件(ui)并保存

启动Designer,出现如图2-12所示界面,选择template\forms中的Dialog with  Buttons Bottom,然后单击【创建】按钮即可完成新建窗体工作。

图2-12 Designer新建窗体

然后,为新建的窗体设置类名:请在窗体空白处单击,然后在属性框中设置对话框的objectName(见图2-13)。设置对话框类名为CDialog,并将UI文件保存为dialog.ui。请记下这两个名称,因为后面会用到。可以根据实际需要对UI文件名、对话框类名进行命名。

图2-13 CDialog属性

然后,从Designer的工具箱的【Display Widgets】选项卡中选择Label控件(类型为QLabel,见图2-14),并拖入窗体。

图2-14 文本控件

双击该Label控件,将文字修改为“This is my dialog!”,如图2-15所示。

图2-15 编辑文本控件内容

然后,单击窗体空白处选中整个窗体,再单击工具栏上的【栅格布局】按钮为窗体设置布局(见图2-16)。

图2-16 对窗体进行栅格布局

2.编写界面ui对应的类CDialog

为dialog.ui编写对应的类CDialog,目的是为对话框增加业务功能。此处的类名CDialog来自图2-13中窗体的objectName。CDialog的头文件dialog.h的代码请见代码清单2-3。

代码清单2-3

// dialog.h

#pragma once                                                                           

#include <QDialog>

namespace Ui {

    class CDialog;                                                                     

}

// 基类的名称来自UI文件中对话框的类名:对象查看器中的类名

class CDialog : public QDialog {                                                       

public:

    CDialog(QWidget* pParent);

    ~CDialog();

private:                

    Ui::CDialog* m_pUi;                                                              

};

请注意代码清单2-3中标号①处:#pragma once。该代码的作用是防止编译该头文件时发生重入的情况(多次编译同一个头文件),以免出现编译错误。

在标号②处,对命名空间Ui中的类CDialog做了前置声明,这是因为标号④处要用Ui::CDialog定义指针m_pUi。使用指针和前置声明有2个好处:一是无须在头文件中包含Ui::CDialog的头文件ui_dialog.h,因为在dialog.cpp中将包含该头文件,这在中大型项目中非常重要,因为这样做可以节省很多编译时间;二是当CDialog类(不是Ui::CDialog)需要作为DLL中的类被引出时,不会导致包含dialog.h的其他项目出现编译错误(比如,找不到ui_dialog.h)。

在标号③处,CDialog类的基类是QDialog,这是因为在Designer中绘制UI文件时选择的就是QDialog。如果不清楚CDialog的基类应该选哪一个,可以在Designer的【对象查看器】中查看。如图2-17所示,第一行对象Dialog的类名QDialog就是标号③处的基类名称。

图2-17 对象查看器

在标号④处,为了使用dialog.ui中的布局,需要为类CDialog添加私有成员Ui::CDialog*  m_pUi,该对象用来初始化界面。Ui::CDialog来自dialog.ui,是通过Qt的uic命令解析dialog.ui后得到的。在项目生成的临时文件ui_dialog.h中可以找到它的定义,见代码清单2-4。在代码清单2-4中,如标号①处所示,注释中明确指出:对该文件的手工修改都将在重新编译UI文件时被覆盖。在标号②、标号③处,是界面中buttonBox、label这两个控件对象的定义。在标号④处,是初始化界面接口setupUi()的实现,在该接口中完成了对界面的初始化,包括对buttonBox、label这两个控件的构建。在标号⑤处,在命名空间Ui中定义类CDialog,该类(Ui::CDialog)将被用来定义对象m_pUi,见代码清单2-3中标号④处。

代码清单2-4

// ui_dialog.h

/********************************************************************************

** Form generated from reading UI file 'dialog.ui'

**

** Created by: Qt User Interface Compiler version 5.11.1

**

** WARNING! All changes made in this file will be lost when recompiling UI file! 

********************************************************************************/

#ifndef UI_DIALOG_H

#define UI_DIALOG_H

#include <QtCore/QVariant>

#include <QtWidgets/QApplication>

#include <QtWidgets/QDialog>

#include <QtWidgets/QDialogButtonBox>

#include <QtWidgets/QLabel>

QT_BEGIN_NAMESPACE

class Ui_CDialog

{

public:

    QDialogButtonBox *buttonBox;                                                  

    QLabel *label;                                                                

    void setupUi(QDialog *CDialog)                                                

    {

        if (CDialog->objectName().isEmpty())

            CDialog->setObjectName(QStringLiteral("CDialog"));

        CDialog->resize(329, 184);

        buttonBox = new QDialogButtonBox(CDialog);

        buttonBox->setObjectName(QStringLiteral("buttonBox"));

        buttonBox->setGeometry(QRect(80, 120, 161, 32));

        buttonBox->setOrientation(Qt::Horizontal);

        buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);

        label = new QLabel(CDialog);

        label->setObjectName(QStringLiteral("label"));

        label->setGeometry(QRect(100, 40, 171, 16));

        retranslateUi(CDialog);

        QObject::connect(buttonBox, SIGNAL(accepted()), CDialog, SLOT(accept()));

        QObject::connect(buttonBox, SIGNAL(rejected()), CDialog, SLOT(reject()));

        QMetaObject::connectSlotsByName(CDialog);

    } // setupUi

    void retranslateUi(QDialog *CDialog)

    {

        CDialog->setWindowTitle(QApplication::translate("CDialog", "Dialog", nullptr));

        label->setText(QApplication::translate("CDialog", "This is my dialog!", nullptr));

    } // retranslateUi

};

namespace Ui {

    class CDialog: public Ui_CDialog {};                                         

} // namespace Ui

QT_END_NAMESPACE

#endif // UI_DIALOG_H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

下面给出CDialog的实现文件dialog.cpp,请见代码清单2-5。

代码清单2-5

// dialog.cpp

#include "dialog.h"

#include "ui_dialog.h" // 头文件名称: dialog.ui -> ui_dialog.h                         ①

CDialog::CDialog(QWidget* pParent) : QDialog(pParent),m_pUi(new Ui::CDialog) {        

    m_pUi->setupUi(this);                                                              

}

CDialog::~CDialog() {

    if (NULL != m_pUi){                                                                

        delete m_pUi;

        m_pUi = NULL;

    }

}

代码清单2-5中,标号①处的代码包含ui_dialog.h头文件,目的是让编译器可以看到Ui::CDialog的定义。这用到了步骤1中保存界面文件时的文件名dialog.ui。Qt的uic命令将dialog.ui文件转换为UI头文件: ui_dialog.h,即前缀ui_加上文件名dailog.ui中的dialog共同拼接成ui_dialog.h。

标号②处,在类CDialog的构造函数的初始化列表中,除了用QDialog(pParent)调用基类的构造函数进行初始化之外,还构建了m_pUi对象。

标号③处,在构造函数中一定要调用m_pUi->setupUi(this),否则界面无法正常显示。因为这处调用就是对界面进行构建。如果对该接口感兴趣,可以单步调试一下这个调用,看看setupUi()内部到底执行了什么操作。

在标号④处,当CDialog析构时需要对m_pUi指向的内存进行释放,并将m_pUi赋值为NULL。

除了将CDialog作为DLL的引出类等特殊需要外,在后续章节中不再限制使用指针还是对象的方式使用Ui::CDilaog。如果使用对象的方式,则需要把dialog.h做两处修改,见代码清单2-6中的标号①处、标号②处,而且dialog.cpp中不再编写#include "ui_dialog.h"的代码。

代码清单2-6

// dialog.h

#pragma once

#include <QDialog>

#include "ui_dialog.h"                                                                 

class CDialog : public QDialog {

    ...

private:

    Ui::CDialog m_ui;                                                                  

};

3.将相关文件添加到pro

目前已完成的工作包括:设计界面文件dialog.ui、编写CDialog类的定义文件dialog.h和实现文件dialog.cpp。现在把这些文件添加到项目的pro文件,见代码清单2-7。

代码清单2-7

// ks02_02.pro

QT += widgets

FORMS = dialog.ui

HEADERS += ks02_02.pro \

           dialog.h                      

SOURCES +=  main.cpp \

            dialog.cpp

代码清单2-7中,FORMS配置项用来描述项目中用到的UI文件列表;HEADERS和SOURCES两个配置项在前面章节介绍过,只不过在本案例中使用了多个文件。因为用到了界面控件,请确保编写QT+=widgets,否则将导致程序构建失败。

4.使用CDialog定义对象

如果用类CDialog定义对象,首先需要包含CDialog的头文件(dialog.h),然后才能定义CDialog的对象并调用其接口,见代码清单2-8。

代码清单2-8

// main.cpp

#include <QApplication>

#include <iostream>

#include "qglobal.h"

#include "dialog.h"

using std::cout;

using std::endl;

int main(int argc, char * argv[]){

    Q_UNUSED(argc);

    Q_UNUSED(argv);

    QApplication app(argc, argv);

    CDialog dlg(NULL);

    dlg.exec();

    return 0;

}

到现在为止,为项目添加界面的工作就结束了,可以构建项目了。

但是当项目构建完成后,看一下源代码目录就会发现临时文件和临时目录太多了(如图图2-18所示方框内的文件或目录),简直杂乱不堪。一般情况下,项目组会使用代码管理工具(比如SVN)管理代码,而且可以预先设置代码入库的过滤条件,因此在提交代码时临时文件或目录不会被入库。但是,当需要备份一下源代码目录并打包时,如果有这么多临时文件(有的临时文件尺寸比较大),那可太不方便了。下面介绍如何通过修改pro文件的配置来整理源代码目录。

图2-18 案例2源代码目录

整理源代码目录的方法是引入环境变量,然后通过环境变量来设置pro中的各种路径。

首先引入一个TRAINDEVHOME的环境变量,它指向项目src的上级目录。代码的目录结构如下。

TRAINDEVHOME

------bin

------obj

------src

其中bin 、obj、src都是TRAINDEVHOME的子目录。bin目录用来存放项目生成的可执行程序和动态链接库,obj是构建项目时生成的临时文件的根目录

在pro文件中使用环境变量的语法:$$(环境变量)。比如:$$(TRAINDEVHOME)。对pro的修改见代码清单2-9。

代码清单2-9

# ks02_02.pro

...

OBJECTS_DIR = $$(TRAINDEVHOME)/obj/chapter02/ks02_02

DESTDIR      = $$(TRAINDEVHOME)/bin

MOC_DIR      = $$OBJECTS_DIR/moc

UI_DIR       = $$OBJECTS_DIR/ui

在代码清单2-9中,OBJECTS_DIR是Qt的关键字,用来表示项目生成的临时文件的存放目录。将OBJECTS_DIR设置到obj下对应本案例的子目录。

DESTDIR是Qt的关键字,用来表示目标文件存放目录,即项目最终生成的EXE或DLL的存放目录。如果是EXE,将其设置到TRAINDEVHOME的bin子目录;如果是DLL,则设置到lib子目录。

MOC_DIR、UI_DIR是Qt的关键字,用来表示Qt的moc和uic命令生成的临时文件存放目录。将它们分别设置到OBJECTS_DIR下面的moc子目录和ui子目录。

除此之外,如果使用Qt Creator进行开发,还需要设置默认构建路径,否则会导致影子构建时生成的临时文件被放到源代码目录中。选择Qt Creator的【工具】|【选项】菜单项,出现【选项】对话框,选择【构建和运行】标签,再选择【概要】标签,最后修改Default build directory(见图2-19),将其值修改为(关于该值的拼写请见本书配套资源中【Qt Creator 4.7.2的Default build directory设置】):

%{CurrentProject:VcsTopLevelPath}/obj/%{CurrentBuild:Name}/%{JS: Util.asciify("build-%{CurrentProject:Name}-%{CurrentKit:FileSystemName}")}

图2-19 Qt Creator默认构建目录

修改pro文件并设置完Qt Creator后,再次构建项目后得到的源代码目录见图2-20。

图2-20 整理后的目录

如果使用VS 2017命令行进行构建,在每次修改pro后请重新执行qmake以便更新相应的Makefile文件;如果用VS 2017的IDE进行构建,则需要更新VS 2017的IDE中的项目文件(后缀为.vcxproj的文件),方法是执行qmake  –tp  vc,然后用VS 2017 的IDE重新加载项目文件。

本案例介绍了开发界面类应用的步骤以及通过修改pro配置来整理源代码目录的方法,现在汇总一下知识点:

(1)如果项目中需要使用界面,那么在pro中请务必添加:

QT  +=widgets

(2)CDialog类的头文件中,请注意CDialog基类名称的来源以及私有的指针成员变量m_pUi以及对于Ui::CDialog的前置声明,见代码清单2-10。

代码清单2-10

namespace Ui {

    class CDialog;

}

class CDialog : public QDialog {

    ...

private:

    Ui::CDialog* m_pUi;

};

(3)请注意dialog.ui对应头文件ui_dialog.h的文件名的构成规则。

(4)CDialog类的构造函数中一定要调用m_pUi->setupUi(this)。

(5)要在pro中通过环境变量设置相关目录。环境变量在使用时应该用下列语法:

        $$(环境变量)

(6)可以在pro中定义变量,变量在使用时应该用下列语法:

        $$变量名称

(7)另外,还介绍了Qt关于路径设置的关键字。

  1. DESTDIR:存放最终生成的目标文件的路径。
  2. OBJECTS_DIR:本项目临时文件的存放目录。
  3. MOC_DIR:用来存放qt的moc命令生成的临时文件。
  4. UI_DIR:用来存放qt的uic命令生成的临时文件。当项目中包含UI文件时需要用到该目录。
  5. FORMS:用来描述项目中包含的UI文件列表。

《Qt 5/PyQt 5实战指南》目录

posted @ 2019-08-30 17:55  女儿叫老白  阅读(259)  评论(0编辑  收藏  举报