在传统的应用程序设计中,我们可以看到很多通过浏览器唤起本地应用的案例,比如百度网盘、迅雷等工具,他们在浏览器访问一个非 http/https 协议开头的地址时,会自动打开其自己的应用程序并传递一定的参数。该功能的实现方式网络上有很多示例,在 Windows 和 macOS 不同平台下他们分别需要如下设置:
Qt Quick Windows 下实现无边框窗口阴影效果
Qt Quick 中实现一个无边框窗口阴影效果的中心思想是将 Window 容器背景设置为透明的,在 Windows 容器中添加一个填满窗口的容器(如 Page、Rectangle 等),然后将这个容器设置一些边距,再给这个容器附加一个阴影效果,实现的效果如下:
注意:本文所描述的方案会有严重的性能损耗,尤其是在 2k 4k 显示器上性能损耗严重、帧率极低,如非特殊需求,建议不使用类似方案。
Qt QML qtquickcontrols2.conf 使用系统未安装的自定义字体
现在很多视觉设计最终给出的字体可能并不是所有系统都安装过的,所以项目中可能需要单独把字体文件打包进去在程序运行的时候加载。Qt QML 有多种方式加载一个自定义字体,本文将介绍所有我知道的方式,大家根据自己的需要选择使用任意一种即可。
Qt Quick QML MouseArea 事件穿透
MouseArea
是 QML 中一个不可见的鼠标操作区域,可响应所有鼠标事件。一般情况下在自定义按钮、自定义需要鼠标交互的区域时使用。有时你只需要它的 hover 通知来做一些事情,而另外的点击等操作需要传递给其下层的控件,这时你就需要忽略其自身的鼠标按下释放等操作让其消息传递到下层了。
Qt Quick QML 中使用自定义字体文件
为了视觉上的统一,有时你需要保持 UI 上的字体与其他端字体一致,但是又不是所有字体所有系统都带有的,所以有时需要加载一个自定义字体来满足需求。本文重点介绍如何加载以及使用自定义字体。
下载并导入字体
下载好你需要的字体文件后通过 Qt Creator 将字体文件导入到资源列表(这样字体会加载到执行程序中,执行程序会比较大):
在导入完字体文件后,需要执行一下 qmake,在项目名称右键->执行qmake
加载字体
首先复制字体路径:
在 main.qml 中增加如下代码(这样全局都可以访问到)
FontLoader {
id: localFont;
source: "qrc:/fonts/PingFang.ttf"
}
字体的 ID 自己随便定义,路径就是上图中拷贝出来的路径,保存后字体就加载成功了。
使用字体
在需要使用字体的位置像如下代码一样,使用 localFont.name 属性来获取字体名称:
Text {
color: "#333333"
font.family: localFont.name
font.pixelSize: 14
textFormat: Text.PlainText
}
这样这个 Text 就使用了我们加载的 PingFang
字体了。
Qt Quick 项目实现根据操作系统语言自动显示指定翻译
Qt 提供了一整套的翻译引擎,讲你需要翻译的内容使用 qsTr(“”) 包括起来就可以通过工具来扫描这些待翻译的内容。我们用 Qt 创建的 Empty 项目来做演示,创建完成后,main.qml 中只有如下代码:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
}
其中 Hello World 文字就使用 qsTr() 括起来了,我们首先需要做的就是让 Qt 翻译工具可以扫描到这些带翻译的文件。
Qt for iOS 应用使用自定义动态库 Framework
本文内容适合当你想使用 Qt 调用 xcode 开发的动态库 framework 做 iOS 应用的场景,文中涉及到原始动态库 framework 文件的配置,以及 Qt 如何引入和打包动态库 framework 到自身程序中的方法。
动态库 framework 的配置
默认 xcode 创建的 framework 都已经是动态库形式了,所以这里不多介绍,请确保你的 Build Settings 中以下设置是无误的,特别是 Dynamic Library Install Name Base
,它决定了动态库能否被应用成功搜索到。
对于 @rpath 的说明,请看这里:https://www.cnblogs.com/csuftzzk/p/mac_run_path.html
Qt 项目配置引入 framework
Qt 引入 framework 只需要给 LIBS 追加 framework 的路径和要链接的文件就可以了,配置如下,请注意使用 framework 参数是 -F 和 -framework(注意大小写)
LIBS += -F$$PWD/../../build/Products/Release
LIBS += -framework my_dylib.framework
这样引入还是不行的,因为 iOS 应用部署上去以后动态库的 framework 文件是不会跟随打包进去的,所以你还需要做一件事情就是把 framework 打包到你的应用中,配置如下:
MY_DYLIB_FRAMEWORK.files = $$PWD/../../build/Products/Release-iphoneos/my_dylib.framework
MY_DYLIB_FRAMEWORK.path = /Frameworks
QMAKE_BUNDLE_DATA += MY_DYLIB_FRAMEWORK
分别指定了 framework 文件的路径和要被打包进去的目标路径,这里是将 $$PWD/../../build/Products/Release-iphoneos/my_dylib.framework 这个 framework 打包到了应用的 /Framework 目录下。最终你看到的应用目录结构是这样的
├── Default-568h@2x.png
├── Frameworks
│ └── my_dylib.framework
│ ├── Info.plist
│ ├── ReadMe.txt
│ ├── _CodeSignature
│ │ └── CodeResources
│ └── my_dylib
├── Info.plist
├── LaunchScreen.nib
├── PkgInfo
├── _CodeSignature
│ └── CodeResources
└── cross-platform-demo
可以看到,mu_dylib.framework 文件已经在我们应用的 /Framework 目录下了,这样文件就被打包进去了,而且当你使用 Qt 编译程序的时候,在 Build Shadow 目录下会看到生成对应的 xcodeproj 文件,可以直接使用 xcode 打开,使用 xcode 打开项目后可以看到项目的 Build Phases 下面多了一条 Copy file to bundle 的项目:
但是当你尝试在 iOS 或者模拟机中运行这个应用时你会发现又有新的错误了,如下所示:
dyld: Library not loaded: @rpath/my_dylib.framework/my_dylib
Referenced from: /var/containers/Bundle/Application/D0143CDE-FFFE-4343-BFD9-D70DA66C831F/cross-platform-demo.app/cross-platform-demo
Reason: image not found
program received signal 6, thread:15362b
我明明已经把文件复制进去了,为什么还是会提示,还记得上面我们提到的 Dynamic Library Install Name Base
吗?这是动态库 framework 设置的,根据上面文章的资料,我们要在调用该模块的应用中设置 rpath 的搜索范围,让其能找到我们的动态库文件。Qt 项目中添加如下配置:
# 添加应用的 runpath 路径,因为 my_dylib 动态库 Framework 设置的 install path 为 rpath,所以应用使用时需要单独设置
QMAKE_LFLAGS += -Wl,-rpath,@loader_path/Frameworks
如此设置后,在 Qt 中就可以成功编译程序并运行在模拟器或真机上了,如果还有任何疑问欢迎留言我们一起讨论。
Qt Quick QSettings 配置信息保存位置
Qt Quick 给我们提供了非常方便的配置文件管理功能,它不仅仅可以在 C++ 中访问,也可以在 QML 中直接访问,最近在看 Qt Examples 目录下的 gallery 项目示例时,虽然知道用的是 QSettings 保存的持久化数据,但是不知道配置保存在哪里了,遂到 Qt 官网查询了一下,有英文阅读能力的可直接参考官网:https://doc.qt.io/qt-5/qsettings.html,以下为照搬翻译:
C++ 和 QML 的正确交互方法
相关资料
- C++ 操作 QML:https://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html
- QML 操作 C++:https://doc.qt.io/qt-5/qtqml-cppintegration-topic.html
- QML 和 C++ 集成:https://doc.qt.io/qt-5/qtqml-cppintegration-overview.html
- QML 动态生成图像:https://doc.qt.io/qt-5/qquickimageprovider.html
- C++ Plugin for QML:https://doc.qt.io/qt-5/qtqml-modules-cppplugins.html
Qt 模拟 HTTP 表单提交文字或文件到服务器
传统通过 HTTP 表单的方式来上传文件在 Web 中实现是非常简单的,一个表单中加几个域填写上对应的内容提交就可以了,但如果通过 Qt 来实现就相对麻烦一点,不过我都总结好了代码,直接使用就可以了。
需要用到的模块
- QNetworkAccessManager 用来发起 GET/POST 请求
- QNetworkReply 用来描述响应信息
- QHttpMultiPart 用来模拟表单域
- QNetworkRequest 用来构建请求地址等信息
Qt QSS 属性选择器使用详解
Qt 的属性选择器是其独有的,非常类似 CSS 的类选择器,但是由于 CSS 的类选择器可以设置多个,所以一个标签只要设置多个的 class 就可以实现不同的效果了甚至重叠效果。但是 Qt 的类选择器没有那么强大,他不能给控件设置多个类标识。所以就有了属性选择器这么个东西。
Qt Creator 项目属性配置常用设置
设置编译后目标保存目录
DESTDIR = bin
设置一些编译过程中临时文件目录
MOC_DIR = tmp/moc
OBJECTS_DIR = tmp/obj
UI_DIR = tmp/ui
RCC_DIR = tmp/rcc
设置头文件的拓展目录
INCLUDEPATH += $$PWD/libs/
根据不同属性链接不同的静态库
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/libs/ -ljsoncpp
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/libs/ -ljsoncpp_d
或者
LIBS += -L$$PWD/libs/
win32 {
CONFIG(debug, debug|release) {
LIBS += -ljsoncpp_d
LIBS += -lnim_cpp_sdk_d
} else {
LIBS += -ljsoncpp
LIBS += -lnim_cpp_sdk
}
}
根据不同操作系统设置平台化差异属性
unix {
target.path = /usr/local/lib
}
win32 {
target.path = C:\Windows
}
INSTALLS = target
Qt 注册自定义数据类型提供信号和槽函数传递参数
Qt 信号和槽函数参数只能是基于 Qt 的基础类型的,比如 QString、int、bool 等,如果想传递自定义类型默认情况下是行不通的。要想在 Qt 的信号和槽函数之间传递自定义类型,可以先将自己的自定义类型注册一下,使用如下代码:
Q_DECLARE_METATYPE(nim::DocTransInfo)
nim::DocTransInfo
为你的自定义数据结构,我这里是一个结构体。当需要传递这个数据时,不是直接使用,而是用 QVariant
来包装一下,信号和槽函数则直接使用 QVariant
类型的数据作为参数传递。首先我们连接信号和槽:
connect(this, SIGNAL(AddDocItemSignalNew(QVariant)), this, SLOT(AddDocItemNew(QVariant)), Qt::QueuedConnection);
信号和槽函数的声明如下:
signals:
void AddDocItemSignalNew(QVariant variant);
private slots:
void AddDocItemNew(QVariant variant);
当要发起信号的时候,把我们要传递的这个数据包装到 QVariant 中,然后传递给信号函数就可以了。如下所示:
QVariant data;
data.setValue(file_info);
emit AddDocItemSignalNew(data);
槽函数接受到信号时可以像如下方法一样解析参数出来使用:
void MainForm::AddDocItemNew(QVariant variant)
{
nim::DocTransInfo doc_info = variant.value<nim::DocTransInfo>();
// ....... 其他代码
}
这样包装后,我们就可以使用 Qt 的信号和槽功能来传递自定义数据结构了。