7. 工程管理 -------------------------------------- Qt 工程文件 pro pri 多项目工程,子项目,跨平台管理 7.1. 经验之谈 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **通用规则** 除了极小的微型demo级别项目外,其余项目建议用pri分门别类不同文件夹存放代码文件,方便统一管理和查找。 同类型功能的类建议统一放在一起,如果该目录下代码文件数量过多,也建议拆分多个目录存放。 比如就3-5个界面的项目,统一搞个form.pri存放这些界面,而当项目越来越大,界面可能也需要按照功能划分,比如系统配置的窗体放在一个目录下,日志管理的窗体放在一个目录下。 很多通用功能,多个项目都会用到,可以考虑封装成pri形式的模块,俗称轮子,不断完善这些轮子,多个项目共享该模块,一旦遇到BUG修复,只需要更改一个地方就行。 项目如果还更大或者项目组人员分配不同功能,可以考虑插件形式,插件一般会用到两种,一种是普通动态库形式的插件,必须和主程序放在一起;一种是Qt机制的插件,放在指定的目录。 全局配置文件 全局配置文件管理类 appconfig.h 用来读写对应项目的配置文件。 格式可以是ini、xml、json等,小项目建议ini,怎么方便怎么来,相当于将配置文件的值映射到全局变量。 配置文件如果配置项较多建议分组存储方便查找,而不是全部放在一个大分组中。 读配置文件的时候可以判断配置文件是否存在、配置项是否缺失等情况,有问题则重新生成配置文件,避免恶意删除配置文件导致程序运行异常。 读配置文件的时候可以填入默认值(qt配置文件类QSettings的value方法的第二个参数,set.value("Hardware", App::Hardware)),避免初始时候读取不到节点而导致配置项值不符合预期值类型。 读配置文件完成后可以重新判断配置项的值是否符合要求,对值进行过滤和矫正,防止人为打开配置文件修改后填入了异常的值,比如定时器的间隔为0,要重新纠正设定为合法的值。 带中文的初始值用QString::fromUtf8包起来,比如QString::fromUtf8("管理员")。 带中文的配置项要设置配置文件编码为 utf-8,set.setIniCodec("utf-8")。 **全局变量** 全局变量管理类 appdata.h 用来设置项目中用到的所有全局变量。 比如当前用户/系统是否锁定等,这样可以在任意的编码位置使用该变量进行判断处理。 可以将UI界面中的导航栏宽高、按钮大小、图标大小等变量放在这,系统启动后判断分辨率等来设定不同的值。 全局事件中转处理 全局事件中转处理类 appevent.h 用来中转系统中各种跨多个UI以及多个类的事件。 此类必须是全局单例类,便于全局统一使用。 比如类a的父类是b,类b的父类是c,现在有个信号要发给类d,在没有事件中转处理的情况下的做法是将a信号发给b,b再发给c,c再发给d,如果父类嵌套层级越多越复杂,代码越难管理。 将类a的信号发给appevent类,然后类d直接关联appevent类进行处理就行。 项目越大,会越发现事件中转处理的必要性,代码清晰,管理方便。 **全局程序初始化** 全局程序初始化类 appinit.h 用来做一些程序启动后的初始化处理。 读取配置文件。 设置全局字体。 设置全局样式表,建议先读取通用的样式表,然后将额外的样式表内容加到后面一起设置。 设置项目编码。 设置翻译文件,可以加载多个,包括qt内置的qt_zh_CN.qm,用户自己的翻译文件等。 初始化随机数种子。 新建项目中需要的目录,防止没有目录无法保存文件到目录。 初始化数据库,包括打开数据库,载入基础数据比如用户表、设备表等。 启动日志输出类用来启动日志服务。 启动运行时间记录类用来记录每次软件运行开始时间和结束时间。 关联全局事件过滤器处理自定义无边框UI拖动、全局按键处理等。 **全局通用类** 调试日志输出类 savelog.h 用来启动日志服务,可以将日志输出到文件或者网络打印输出。 运行时间记录类 saveruntime.h 用来记录每次软件运行开始时间和结束时间。 图形字体类 iconfont.h 用来设置图形字体图标。 7.2. 工程目录 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: c++ :caption: .pro :linenos: ├── xxxx.pro # Readme ├── .gitignore.md # 工程配置文件 ├── README.md # git 可忽略的中间文件 ├── api # 本工程特有的代码,函数,类 │   ├── api.pri # 代码模块配置文件 │   ├── *.cpp │   └── *.h ├── common # 可复用移植的代码,函数,类 如 日志类,加密解密类 │   ├── common.pri │   ├── *.cpp │   ├── *.h │   ├── resources │   └── res.qrc ├── * # 本工程使用到其他模块 如OpenCV等等 ├── ui # UI代码、文件 │ ├── base # 可复用移植的的界面,控件 │ │   ├── base.pri # 控件设计/效果 │ │   ├── *.cpp │ │   └── *.h │ ├── ui.pri # UI设计/效果 │   ├── *.cpp │  ├── *.h │ ├── *.rp │ └── source # 资源文件 │    └── * ├── app │   ├── app.pri │   ├── main.cpp │   ├── main.qrc │   ├── mainwindow.cpp │   ├── mainwindow.h │   ├── mainwindow.ui │   └── resources │   ├── classcell.mmap │   ├── classcell.vsdx │   ├── css │   │   ├── *.css │   ├── qm │   │   ├── language.en.qm │   │   ├── language.en.ts │   │   ├── language.zh.qm │   │   └── language.zh.ts │   ├── rc │   │   ├── app.rc │   │   └── main.ico │   ├── question.txt │   └── version.txt ├── build │   └── classcell │   └── *.o ├── demo │   ├── ClassCell │   ├── ClassCell.pdb │   ├── conf │   │   └── config.ini │   ├── language │   │   ├── language.en.qm │   │   └── language.zh.qm │   ├── log │   │   ├── log_2021-08-20.txt │   │   └── runtime_2021.txt │   ├── res │   │   └── fontawesome-webfont.ttf │   ├── *.dll │   ├── *.exe │   └── *.lib └── doc    ├── doc.conf    ├── html    └── * example .. image:: image/project001.png :alt: project001.png :align: center .. tip:: #. 使用 git 管理工程 #. 使用不同目录管理不同的代码模块,方便测试、移植 #. Qt 跨平台项目,将依赖库直接放入工程中管理,切换平台或电脑,不需要重新配置环境(嵌入式环境编译库比较麻烦),不足之处是需要对依赖库比较了解。 #. 不使用 QtCreator 默认的构建目录,自定义 build 中间目录和demo目录,demo目录放入程序运行的依赖库,方便打包程序。 #. 引入 Doxygen 来管理注释生成文档。 #. common 和 ui 模块为可复用代码,多个工程可通用。也可以整理自己的通用库。 7.3. 工程结构 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ pro pri SUBDIRS .. code-block:: c++ :caption: ClassCell.pro :linenos: QT += core gui QT += serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 CONFIG -= debug_and_release # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 include($$PWD/app/app.pri) include($$PWD/common/common.pri) include($$PWD/toupcam/toupcam.pri) include($$PWD/ui/ui.pri) include($$PWD/api/api.pri) include($$PWD/opencv/opencv.pri) include($$PWD/gsl/gsl.pri) TARGET = ClassCell TEMPLATE = app DESTDIR = $$PWD/demo MOC_DIR = $$PWD/build/classcell UI_DIR = $$PWD/build/classcell RCC_DIR = $$PWD/build/classcell OBJECTS_DIR = $$PWD/build/classcell win32:RC_FILE = $$PWD/app/resources/rc/app.rc TRANSLATIONS = \ $$PWD/app/resources/qm/language.zh.ts $$PWD/app/resources/qm/language.en.ts win32{ # opencv INCLUDEPATH += $$PWD/opencv/includes/opencv2/ LIBS += $$PWD/opencv/mingw/winlib/libopencv_*.a } #DEFINES += __ARM__ #DEFINES += __UBUNTU__ contains(DEFINES, __ARM__){ # arm unix{ # opencv LIBS += -L$$PWD/usr/local/lib -lopencv_calib3d -lopencv_core -lopencv_dnn LIBS += -L$$PWD/usr/local/lib -lopencv_features2d -lopencv_flann -lopencv_gapi LIBS += -L$$PWD/usr/local/lib -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc LIBS += -L$$PWD/usr/local/lib -lopencv_ml -lopencv_objdetect -lopencv_photo LIBS += -L$$PWD/usr/local/lib -lopencv_stitching -lopencv_video -lopencv_videoio } } contains(DEFINES, __UBUNTU__){ # ubuntu unix{ # opencv LIBS += -L$$PWD/opencv/mingw/linuxlib -lopencv_calib3d -lopencv_core -lopencv_dnn LIBS += -L$$PWD/opencv/mingw/linuxlib -lopencv_features2d -lopencv_flann -lopencv_gapi LIBS += -L$$PWD/opencv/mingw/linuxlib -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc LIBS += -L$$PWD/opencv/mingw/linuxlib -lopencv_ml -lopencv_objdetect -lopencv_photo LIBS += -L$$PWD/opencv/mingw/linuxlib -lopencv_stitching -lopencv_video -lopencv_videoio } } # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target .. tip:: pro pri 关键字可参考 本工程目录下 pdf/QT中.pro变量解析.pdf 或访问官方有参考文档。 gsl 模块 .. code-block:: c++ :caption: gsl.pri :linenos: DEFINES += GSL_DLL INCLUDEPATH += $$PWD/includes LIBS+= -L$$PWD/winlib/mingw81_32/lib -llibgsl LIBS+= -L$$PWD/winlib/mingw81_32/lib -llibgslcblas HEADERS += \ $$PWD/includes/gsl/gsl_sf_bessel.h \ $$PWD/includes/gsl/gsl_mode.h \ $$PWD/includes/gsl/gsl_precision.h \ $$PWD/includes/gsl/gsl_sf_result.h **模块测试** .. code-block:: c++ :caption: gsl.pro :linenos: QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 CONFIG -= debug_and_release # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 include($$PWD/gsl.pri) TARGET = Gsltest TEMPLATE = app DESTDIR = $$PWD/../demo MOC_DIR = $$PWD/../build/gsltest UI_DIR = $$PWD/../build/gsltest RCC_DIR = $$PWD/../build/gsltest OBJECTS_DIR = $$PWD/../build/gsltest SOURCES += \ $$PWD/main.cpp .. code-block:: c++ :caption: main.pro :linenos: #include //#include #include #include "math.h" #include int main(int argc, char *argv[]) { QCoreApplication a(argc,argv); double x=10; double y=gsl_sf_bessel_J0(x); std::cout<<"J0("<< x <<")= "< class QProcess; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void openProcess(); void readResult(int exitCode); private: QProcess *p; }; #endif // MAINWINDOW_H .. code-block:: C++ #include #include #include #include #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { p = new QProcess(this); QPushButton *bt = new QPushButton("display", this); connect(bt, SIGNAL(clicked()), this, SLOT(openProcess())); } MainWindow::~MainWindow() { } void MainWindow::openProcess() { #if defined(Q_OS_WIN32) p->start("cmd.exe", QStringList() << "/c" << "dir"); #elif defined(Q_OS_LINUX) p->start("ls", QStringList() << "/home/usr_name"); #endif connect(p, SIGNAL(finished(int)), this, SLOT(readResult(int))); } void MainWindow::readResult(int exitCode) { if(exitCode == 0) { #if defined(Q_OS_WIN32) QTextCodec* gbkCodec = QTextCodec::codecForName("GBK"); QString result = gbkCodec->toUnicode(p->readAll()); #elif defined(Q_OS_LINUX) QTextCodec* utfCodec = QTextCodec::codecForName("UTF-8"); QString result = utfCodec->toUnicode(p->readAll()); #endif QMessageBox::information(this, "dir", result); } }