Content Table

Single Application

如果限制一个程序同时只能启动一个实例,有几个可以使用的库

观察者模式的 NotificationCenter

Qt 的信号槽在解耦方面做的非常好,sender 和 receiver 不需要互相知道,但是在使用 QObject::connect() 建立信号槽连接的时候是必须要同时知道 sender 和 receiver 的,这在绝大多数时候都是非常好用的,但是如果只想发送通知后,对此通知感兴趣的监听者就能自动的收到通知,同时还不用 QObject::connect() 建立信号槽连接,就可以使用下面介绍的 NotificationCenter 来实现。此外,NotificationCenter 内部已经处理好了跨线程通讯,不需要再考虑不同线程间函数调用的头疼问题。

NotificationCenter 的使用:

  • 通知的监听器,不需要使用继承,只需要实现函数 notified,当有通知的时候,notified 函数会被自动调用

    1
    Q_INVOKABLE void notified(int notificationId, const QByteArray &data);
  • 监听某个通知,通知的标志是一个整数,可以根据业务随意定义

    1
    Singleton<NotificationCenter>::getInstance().addObserver(1, &foo);
  • 发送通知, 只需要和 NotificationCenter 交互就能发送和接收通知

    1
    2
    Singleton<NotificationCenter>::getInstance().notify(1, QString("Two").toUtf8()); // 同线程发送消息
    NOTIFY_CROSS_THREAD(2, QString("Two").toUtf8()); // 跨线程发送消息,也可同线程
  • 删除监听器,不再需要的时候,从 NotificationCenter 删除监听器,以免造成野指针异常

    1
    2
    Singleton<NotificationCenter>::getInstance().removeObserver(&foo);
    Singleton<NotificationCenter>::getInstance().removeObserver(1, &foo);

NotificationCenter 什么时候使用? 以 QTcpSocket 通讯为例:
使用信号槽和 NotificationCenter 都可以完成任务,但是 NotificationCenter 也是个不错的选择

  • 如果每一种消息都对应一个信号,则一般都会对应一个槽函数(对创建太多函数总有莫名的恐惧感)
  • 消息的种类会很多,就会需要太多的信号槽
  • 需要使用 connect() 給信号槽对应的创建连接
  • 如果要增加新的消息类型时,则要创建它的槽函数和建立连接
  • 也许你会说,所有的消息都用同一个信号和槽函数,因为可以在发射时传入消息的种类,在槽函数里也是能区别消息的。是的,这确实是一个不错的方法,这种做法和我们的 NotificationCenter 其实就差不多一样了,但是有点区别的是它会把所有的消息都发送到槽函数里,不需要的也会发送,例如有 100 种消息,即使只对其中 2 种消息感兴趣,但是它也会收到另外的 98 种消息。 而使用 NotificationCenter 时只会收到你感兴趣的 2 种消息。
  • 还有一点,NotificationCenter 中通知的监听器注册只需要关心通知的 ID,这个 ID 是根据业务需求定义的一个整数,不需要知道通知的发送者,但是建立信号槽连接时 sender 和 receiver 都需要知道,这也是相对方便的一点。

文中的 监听器观察者 指的是同一个意思;消息通知 也是同一个意思。

ForkLift 记住 FTP 密码

ForkLift 连接 FTP: 菜单 Go > Connect...,这种方式连接 FTP 不会自动记住密码,下次再连接时需要再次输入密码。

为了记住密码,可以使用 Favorites 来管理 FTP 的链接: Favorites > Show Favorites > +,不要勾选 Ask 就可以了

Qt 的 Json 读写工具类 Json

读取下面 Json 文件 x.json 中 admin 属性下的 roles 属性,使用工具类 Json,支持带 “.” 的路径格式,代码如下

1
2
Json json("x.json", true);
qDebug() << json.getStringList("admin.roles");

如果使用原生的 QJsonDocument 来读的话,则代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
QByteArray json; // json 的内容
QFile file("x.json"); // Json 的文件

// [1] 读取 Json 文件内容
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
json = file.readAll();
} else {
return -1;
}

// [2] 解析 Json 得到根节点的 QJsonObject
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(json, &error);
QJsonObject root = jsonDocument.object();

if (QJsonParseError::NoError != error.error) {
qDebug() << error.errorString() << ", Offset: " << error.offset;
}

// [3] 按路径访问得到 roles 的数组
QJsonArray roles = root.value("admin").toObject().value("roles").toArray();

QStringList result;

// [4] 遍历 roles 数组得到数组中的所有字符串
for (QJsonArray::const_iterator iter = roles.begin(); iter != roles.end(); ++iter) {
QJsonValue value = *iter;
result << value.toString();
}

qDebug() << result;

相比之下,类 Json 简单很多,省去了很多繁杂的步骤。

绝对坐标布局

如图所使用的布局在管理系统、监控、统计平台以及对话框中经常使用

主要是使用绝对坐标设置 top, left, right, bottom 和高宽来布局。

弹出层 Layer

Layer 是一款近年来备受青睐的 web 弹层组件,她具备全方位的解决方案,致力于服务各水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验,只依赖于 jQuery,提供了多种的弹出层选择,对 iframe 支持友好,可去其官网体验一下具体的例子 http://layer.layui.com

  • LeanModal 是一个很简单的弹出层,虽然小,但是太过简陋
  • 使用 Bootstrap 的时候,BootstrapDialog 是不错的选择,但是 Layer 还提供了 tips,msg 等特有的弹出层方式,更简单的自定义等。

Qt 杂谈

我们所做的事,所写的代码,都已经被其他人 * 过无数次了,在这里只不过是用我们自己的方式再演绎一次,人生如戏,全靠演技。

我不擅长于写系统的资料,所以这里很多内容看上去都比较散,更像是技术点的集合、提示、思考与总结,目标人群是有一定 Qt 基础的 Qt 爱好者。

文笔也是我的一大缺点,很是粗糙,想到哪写到哪,不成体系,甚至不同章节风格迥异,一会来个漫画,一会来个网络段子,一会神棍杂谈,宇宙洪荒大道理瞎扯蛋,目的只是用散射的思维方式从侧面启发提示相似的道理,主要是我经常天马行空的乱想如斯,实是不知如何才能表达的更贴切,希望大家多多包涵,透过现象看本质。

非 UI 线程中更新 UI

在非 UI 线程中更新 UI (例如改变 QLabel 的文本) 应该使用 信号槽 或者 QMetaObject::invokeMethod(),不要直接调用 widget 的函数,例如在非 UI 线程中直接调用 QLabel::setText(text) 就有可能让程序崩溃。有意思的是 Qt 4 时程序会直接奔溃退出,很容易发现问题,但在 Qt 5 里有时候没问题,有时候会在控制台有警告,有时候程序会退出,导致问题隐藏的比较深,所以最好的办法就是遵守规则不要直接调用,下面的程序展示了相关测试代码。

Because of limitations inherited from the low-level libraries on which Qt’s GUI support is built, QWidget and its subclasses are not reentrant. One consequence of this is that we cannot directly call functions on a widget from a secondary thread. If we want to, say, change the text of a QLabel from a secondary thread, we can emit a signal connected to QLabel::setText() or call QMetaObject::invokeMethod() from that thread. For example:

1
2
3
4
5
void MyThread::run() {
...
QMetaObject::invokeMethod(label, SLOT(setText(const QString &)), Q_ARG(QString, "Hello"));
...
}