Qt 编程技巧与问题总结

整理一些 Qt 编程技巧

1. qmake#

1.1 工程文件#

扩展名 意义 备注
pro project 普通工程文件
pri project include 将 pro 中文件分成多个模块,或提取 pro 中共同的设置
prf project feature 在 Qt 安装目录 mkspecs 下,如 features/spec_post.prf
prl project link 在 Qt 安装目录 lib

1.2 变量#

常用指令 备注
CONFIG release/debug
ordered
c++11
qt
Windows
console
shared/dll
app_bundle
* Build mode, if both are specified, the last one takes effect.
* Specify build order when using the subdirs template.
* C11 support is enabled.
* The target is a Qt application or library and requires the Qt library and header files.
* The target is a Win32 window application (app only).
* The target is a Win32 console application (app only). For a cross-platform command line application, use cmdline.
* The target is a shared object/DLL.
* Puts the executable into a bundle (this is the default).
DEFINES - qmake adds the values of this variable as compiler C preprocessor macros (-D option).
DESTDIR - Specifies where to put the target file.
HEADERS - Defines the header files for the project.
RC_ICONS myappico.ico Setting the Application Icon on Windows
RC_FILE myapp.rc Setting the Application Icon on Windows
myapp.rc content:IDI_ICON1 ICON DISCARDABLE "myappico.ico"
ICON myapp.icns Setting the Application Icon on macOS
INCLUDEPATH - Specifies the #include directories which should be searched when compiling the project.
LIBS - Specifies a list of libraries to be linked into the project.
QT - Specifies the Qt modules that are used by your project.
SOURCES - Specifies the names of all source files in the project.
SUBDIRS - This variable, when used with the subdirs template specifies the names of all subdirectories or project files that contain parts of the project that need to be built.
TARGET - Specifies the name of the target file. Contains the base name of the project file by default.
TEMPLATE
app
lib
subdirs
aux
Specifies the name of the template to use when generating the project.
* Creates a Makefile for building applications (the default).
* Creates a Makefile for building libraries.
* Creates a Makefile for building targets in subdirectories.
* Creates a Makefile for not building anything. Use this if no compiler needs to be invoked to create the target; for instance, because your project is written in an interpreted language.
QMAKE_POST_LINK - Specifies the command to execute after linking the TARGET together.

1.3 函数#

1.3.1 Replace Functions#

qmake provides functions for processing the contents of variables during the configuration process. These functions are called replace functions. Typically, they return values that you can assign to other variables. You can obtain these values by prefixing a function with the $$ operator. Replace functions can be divided into built-in functions and function libraries.

常用函数 备注
shell_path(path) 转换 path 中目录分隔符以兼容当前 shell,如在 Windows 上,将 '/' 转为 '\'
shell_quote(arg) 用 "" 将 arg 包含起来以兼容当前 shell,在 Windows 上,如果 arg 中包含空格,则有 “",否则没有
escape_expand(arg1 [, arg2 ..., argn]) 扩展转义序列,如将 '\r' 转为 '\\r'

1.3.2 Test Functions#

Test functions return a boolean value that you can test for in the conditional parts of scopes. Test functions can be divided into built-in functions and function libraries.

常用函数 备注
CONFIG(config) This function can be used to test for variables placed into the CONFIG variable.
contains(variablename, value) Succeeds if the variable variablename contains the value value; otherwise fails.

1.4 高级用法#

1.4.1 Adding Compilers#

new_moc.output  = moc_${QMAKE_FILE_BASE}.cpp
new_moc.commands = moc ${QMAKE_FILE_NAME} -o ${QMAKE_FILE_OUT}
new_moc.depend_command = g++ -E -M ${QMAKE_FILE_NAME} | sed "s,^.*: ,,"
new_moc.input = NEW_HEADERS
QMAKE_EXTRA_COMPILERS += new_moc

在实际使用中,需要注意 input 参数的值需要通过变量指定。测试直接指定不生效,不明原因。

1.5 undocumented#

spec_post.prf 中定义的一些跨平台的变量,如:

  • QMAKE_COPY
  • QMAKE_COPY_DIR
  • QMAKE_MOVE
  • QMAKE_DEL_FILE
  • QMAKE_DEL_DIR
  • QMAKE_DEL_TREE
  • QMAKE_CHK_EXISTS
  • QMAKE_CHK_DIR_EXISTS
  • QMAKE_MKDIR

Hope this all makes your jobs a little easier…!

2. Qt Installer Framework (IFW)#

客户端程序的安装包制作工具有很多,在 Windows 平台上有:NSISInno Setup,在 Unix 平台上各种包管理器:apt-get、yum。为了降低发布跨平台应用的成本,以及提供跨平台一致性用户体验,Qt 提供了 IFW。

IFW 可分为 Online Installer 和 Offline Installer 或者两者兼而有之。一般情况下,如果需要安装的组件少可以做成离线安装包,然后在 config.xml 中添加 Repository Url 以便通过 maintenance tool 添加、更新、移除组件;如果组件比较多且其中有些组件是用户可选安装的,则可以做成在线安装包,这样既可以减少安装包大小,又可以减少用户下载大小及安装时长。

详细内容请参阅官方手册

3. 调整 Qt 应用程序目录#

对于我们发布的 Qt 应用,通常是使用 windeployqt 抽取依赖的 Qt 库到应用程序根目录。这使得根目录变得“杂乱不堪”,而如果我们将 Qt 库放到单独文件夹则应用程序无法启动。

在 Windows 平台,可以通过添加 Qt 库到环境变量 Path 中解决上述问题;或者更激进的做法,把 Qt 库放到 Windows 目录或者 system32/SysWOW64 目录下。

当然,上述做法严重“污染”了系统的动态库“生态”。导致一系列不可预知的问题。

解决问题的最根本最核心的依据就是 [Windows 动态库搜索顺序][2]。下面介绍一种比较正常的解决方案:

首先,我们需要一个启动程序,它用来设置当前进程的环境变量,将 Qt 库目录和其他 Qt 应用程序依赖的动态库目录设置到 Path 中,然后再通过 CreateProcess 启动 Qt 应用程序。注意 lpEnvironment 参数设置为空,这样子进程就会继承当前进程的环境变量。

这时 Qt 应用仍然不能启动,我们需要一个神奇的配置文件。在 Qt 应用所在的目录创建一个 qt.conf 文件,其内容如下:

[Paths]
Plugins = Path/To/Qt

经过上述两步,我们就可以通过启动程序来启动 Qt 应用了,看着变得干净清晰的目录,是不是很开心。

另外再说明一点,上述正常的解决方案在使用了激进做法的机器上是不能启动的,当然如果依赖的 Qt 版本一致则无问题。

遇到这种情况有两种解决方案:

  1. 将 Qt 库重新放回到应用程序根目录
  2. 要求客户将 Windows /system32/SysWOW64 目录下的 Qt 库移除

4. Qt 编程#

4.1 QMetaEnum#

像 C# 可以获取枚举名称、值、甚至枚举值的描述,对枚举进行遍历的操作。在 Qt 中,如果需要对枚举遍历时,枚举类型要满足以下要求:

  1. 枚举类型声明在类中
  2. 该类继承自 QObject
  3. 该类使用宏 Q_OBJECT
  4. 枚举通过 Q_ENUM(YourEnumType) 声明

满足以上要求的枚举可以通过 QMetaEnum::fromType<YourEnumType>() 获取到 QMetaEnum 对象,该对象可以实现对枚举的遍历、枚举名称和值互查。

4.2 隐藏类实现细节#

Qt 源码中有大量的 Private 类和 d_ptrq_ptr,这就是 Qt 中隐藏类实现细节的一种编程模式。

这种编程模式有如下好处:

  1. 类对外接口更清晰,即头文件更简洁,只需声明类对外提供的接口即可。
  2. 如果类的实现细节有更改,用户不必重新编译源程序,只需更新改动的类库即可。

以下是示例代码:

a.h

//前置声明
class APrivate;
class A
{
public:
    A();

private:
    APrivate *d_ptr;
    Q_DECLARE_PRIVATE(A)
};

a.cpp

class APrivate
{
public:
    APrivate(A *p) : q_ptr(p)
    {

    }

private:
    A *q_ptr;
    Q_DECLARE_PUBLIC(A)
};

class A
{
public:
    A() : d_ptr(new APrivate(this))
    {

    }
};

[2]: https://docs.microsoft.com/en-us/ Windows /win32/dlls/dynamic-link-library-search-order#standard-search-order-for-desktop-applications

❤️ 如果这篇文章对你有帮助,欢迎赞助支持我继续维护 ❤️

☕ Support me ⚡ 爱发电赞助