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. 工程目录
├── 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
Tip
使用 git 管理工程
使用不同目录管理不同的代码模块,方便测试、移植
Qt 跨平台项目,将依赖库直接放入工程中管理,切换平台或电脑,不需要重新配置环境(嵌入式环境编译库比较麻烦),不足之处是需要对依赖库比较了解。
不使用 QtCreator 默认的构建目录,自定义 build 中间目录和demo目录,demo目录放入程序运行的依赖库,方便打包程序。
引入 Doxygen 来管理注释生成文档。
common 和 ui 模块为可复用代码,多个工程可通用。也可以整理自己的通用库。
7.3. 工程结构
pro pri SUBDIRS
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 模块
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
模块测试
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
#include <QCoreApplication>
//#include <gsl/gsl_sf_bessel.h>
#include <gsl/gsl_sf_bessel.h>
#include "math.h"
#include <iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc,argv);
double x=10;
double y=gsl_sf_bessel_J0(x);
std::cout<<"J0("<< x <<")= "<<y<<std::endl;
return a.exec();
}
Tip
配置文件中添加注释 快捷键:Ctrl + /
7.4. 跨平台
工程配置库,不同平台下的库隔离
直接定义一个宏;用的时候可以直接判断,这样做不好的地方是编译前需要重新切换一下宏
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
}
}
代码中区分多平台,通过不同的宏来实现。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
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
#include <QProcess>
#include <QPushButton>
#include <QMessageBox>
#include <QTextCodec>
#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);
}
}