学习 QT 的目的只是为了可以实现跨平台的具有GUI 的程序,以前用的 MFC,但是无法应用在嵌入式平台。后来在全志的 Tina 系统中有看到 QT ,因此特地去了解了QT,挺有意思的,UI也可以做到很漂亮,因此开始学习 QT 之旅。
按照视频课程顺序,摘录比较重要的知识点以及心得,下图给出的代码并非完全照搬视频示例代码,而是根据自己的好奇所编写的测试代码。
本着先学会用,再深入的原则,因此本系列笔记不会太过深入QT的内部实现原理。
视频教程:https://www.bilibili.com/video/av54523708
下载连接:https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.14/5.14.1/qt-opensource-windows-x86-5.14.1.exe
帮助文档:X:\Qt\Qt5.14.1\5.14.1\mingw73_64\bin\assistant.exe
安装之后,会发现代码提示失效,搜了一下资料,只需要移除 ClangCodeModel 插件即可(原因还不清楚):
【帮助】->【关于插件】->【已安装的插件】->[ClangCodeModel] 的勾去掉,再重启IDE即可。
将会以此为基础来做各种各样的实验
#include "widget.h"
#include
int main(int argc, char *argv[])
{
// 应用程序对象, 有且仅有一个
QApplication a(argc, argv);
// 窗口对象
Widget w;
// 默认不会显示, 必须调用 show 方法显示窗口
w.show();
// 进入消息循环
return a.exec();
}
#ifndef WIDGET_H
#define WIDGET_H
#include
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
一个较为直观的按钮使用和按钮事件响应的方法
#include
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
testButton();
}
void Widget::testButton()
{
QPushButton *pButton = new QPushButton();
// 按钮设置
pButton->setText("退出"); // 设置按钮文本
pButton->move(100, 100); // 移动按钮位置
// 设置按钮的父窗口
pButton->setParent(this);
// 设置窗口
// 带不带 this 都一样, 都是表示 Widget 里面的成员方法
// 我比较喜欢带 this, 因为带 this 可以利用 IDE 的代码提示提升效率
this->setWindowTitle("我的第一个QT程序"); // 设置窗口标题
this->resize(640, 480); // 设置窗口大小
// 将按钮的 clicked 信号连接到 Widget 的 close 槽中, 实现点击按钮关闭窗口效果
// connect(指定信号源, 指定信号类型, 指定接收源, 指定接收源中的处理方法)
connect(pButton, QPushButton::clicked, this, Widget::close);
}
1. 通过 QtCreator 的代码提示(注意那个小图标)
2. 查阅帮助文档
如果该类没有找到信号描述,那么就找它的父类看看,如 QPushButton
QT 信号槽是一个很有意思的机制,它提供一种对象与对象之间完全解耦的通讯机制。
下面是以 CAEEngine 来作为信号源,AudioProcess 作为接受源和提供信号处理方法。
1. 自定义信号
#ifndef CAEENGINE_H
#define CAEENGINE_H
#include
#include
#include
class CAEEngine : public QObject
{
Q_OBJECT
public:
explicit CAEEngine(QObject *parent = nullptr);
private:
// 进行周期性发送信号
std::thread mThreadTimerWake;
static void thTimerSendSignal(CAEEngine *pCAEEngine);
public:
void test();
signals: // 表示下面的所有声明指的是信号
// 返回值必须为 void 类型, 不可以有实现, 可以重载
void onSignalWake();
void onSignalWake(int angle, float power);
void onSignalLoadASRCommand(std::string text);
};
#endif // CAEENGINE_H
#include
#include
#include
#include "caeengine.h"
CAEEngine::CAEEngine(QObject *parent) : QObject(parent)
{
// 由于 onSignalLoadASRCommand() 使用了 string 类型, 需要在此注册, 如果使用的是 QString 就不需要
// 如果缺少此句代码,编译不会报错,但是运行到 connect 的时候会输出错误信息,提示信号连接失败。
qRegisterMetaType<std::string>("std::string");
}
void CAEEngine::thTimerSendSignal(CAEEngine *pCAEEngine)
{
unsigned int count = 0;
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "timer start...";
while(1)
{
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "send signal start...";
// emit 来触发信号
emit pCAEEngine->onSignalWake();
emit pCAEEngine->onSignalWake(count++, 9.9);
emit pCAEEngine->onSignalLoadASRCommand("你好");
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "send signal stop...";
Sleep(1000);
}
}
void CAEEngine::test()
{
mThreadTimerWake = std::thread(thTimerSendSignal, this);
mThreadTimerWake.detach();
}
2. 自定义槽
#ifndef AUDIOPROCESS_H
#define AUDIOPROCESS_H
#include
#include
class AudioProcess : public QObject
{
Q_OBJECT
public:
explicit AudioProcess(QObject *parent = nullptr);
public: // 早期的 QT 版本, 必须是 “public slots:”, 高版本不需要
// 返回值必须是 void, 并且需要有具体的实现
void onWakeMessage();
void onWakeMessage(int angle, float power);
void onLocalASRCommandMessage(std::string text);
signals:
};
#endif // AUDIOPROCESS_H
#include
#include
#include "audioprocess.h"
AudioProcess::AudioProcess(QObject *parent) : QObject(parent)
{
}
void AudioProcess::onWakeMessage()
{
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "...";
}
void AudioProcess::onWakeMessage(int angle, float power)
{
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "angle:" << angle << " power:" << power;
}
void AudioProcess::onLocalASRCommandMessage(std::string text)
{
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "text:" << text.c_str();
}
3. 使用方法
#include "caeengine.h"
#include "audioprocess.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->testButton();
this->testSignal();
}
void Widget::testSignal()
{
CAEEngine *pCAEEngine = new CAEEngine();
AudioProcess *pAudioProcess = new AudioProcess();
// 方式一: 没有函数重载的方式
connect(pCAEEngine, CAEEngine::onSignalLoadASRCommand, pAudioProcess, AudioProcess::onLocalASRCommandMessage);
#if 0
// 方式二: 有函数重载的方式, 不能采用方式一, 因为名称相同无法确定是使用哪个函数, 需要借助函数指针来确定是哪种类型
void (CAEEngine::*pFuncWakeSignal)() = CAEEngine::onSignalWake;
void (AudioProcess::*pFuncWakeMessage)() = AudioProcess::onWakeMessage;
connect(pCAEEngine, pFuncWakeSignal, pAudioProcess, pFuncWakeMessage);
void (CAEEngine::*pFuncWakeSignalAnglePower)(int angle, float power) = CAEEngine::onSignalWake;
void (AudioProcess::*pFuncWakeMessageAnglePower)(int angle, float power) = AudioProcess::onWakeMessage;
connect(pCAEEngine, pFuncWakeSignalAnglePower, pAudioProcess, pFuncWakeMessageAnglePower);
#endif
#if 0
// 方式三:可以只保留参数类型,这样看起会简短些
void (CAEEngine::*pFuncWakeSignalAnglePower)(int, float) = CAEEngine::onSignalWake;
void (AudioProcess::*pFuncWakeMessageAnglePower)(int, float) = AudioProcess::onWakeMessage;
connect(pCAEEngine, pFuncWakeSignalAnglePower, pAudioProcess, pFuncWakeMessageAnglePower);
#endif
#if 0
// 方式四:可以不需要定义中间变量,直接通过类型强制转换的方式来指明信号类型和信号处理函数
connect(pCAEEngine, ( void (CAEEngine::*)() )CAEEngine::onSignalWake, pAudioProcess, ( void (AudioProcess::*)() )AudioProcess::onWakeMessage);
connect(pCAEEngine, ( void (CAEEngine::*)(int angle, float power) )CAEEngine::onSignalWake, pAudioProcess, (void (AudioProcess::*)(int angle, float power))AudioProcess::onWakeMessage);
#endif
#if 1
// 方式五:结合方式三和方式四,通过强转和只保留参数类型来实现, 更加简洁
connect(pCAEEngine, ( void (CAEEngine::*)() )CAEEngine::onSignalWake, pAudioProcess, ( void (AudioProcess::*)() )AudioProcess::onWakeMessage);
connect(pCAEEngine, ( void (CAEEngine::*)(int, float) )CAEEngine::onSignalWake, pAudioProcess, ( void (AudioProcess::*)(int, float) )AudioProcess::onWakeMessage);
#endif
// 开始启动线程进行周期性触发信号
pCAEEngine->test();
}
从输出的 “send signal start ” 和 “send signal end” 时间可以发现,QT 的信号槽是异步机制,不是直接的函数回调方式。
void Widget::testSignal()
{
CAEEngine *pCAEEngine = new CAEEngine();
AudioProcess *pAudioProcess = new AudioProcess();
// 方式一: 没有函数重载的方式
connect(pCAEEngine, CAEEngine::onSignalLoadASRCommand, pAudioProcess, AudioProcess::onLocalASRCommandMessage);
// 开始启动线程进行周期性触发信号
pCAEEngine->test();
}
一开始程序界面处于无响应状态
输出的调试消息(注意打印的时间):
还没有看相应的源代码,因此根据实验现象大胆推测如下几点:
所以,耗时的动作应该有子线程进行,槽函数通过唤醒子线程的方式来做,就不会影响 GUI。
#ifndef CAEENGINE_H
#define CAEENGINE_H
#include
#include
#include
class CAEEngine : public QObject
{
Q_OBJECT
public:
explicit CAEEngine(QObject *parent = nullptr);
private:
// 进行周期性发送信号
std::thread mThreadTimerWake;
static void thTimerSendSignal(CAEEngine *pCAEEngine);
public:
void test();
public:
void triggerSignal();
signals: // 表示下面的所有声明指的是信号
// 返回值必须为 void 类型, 不需要具体实现, 可以重载
void onSignalWake();
void onSignalWake(int angle, float power);
void onSignalLoadASRCommand(std::string text);
// 新增加两个信号
void onSignalCAEEnable(bool is);
void onSignalCAEDisable(bool is);
};
#endif // CAEENGINE_H
void AudioProcess::onCAEEnableMessage(bool is)
{
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "is:" << is;
}
void AudioProcess::onCAEDisableMessage(bool is)
{
qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") << __FILE__ << "->" << __FUNCTION__ << "is:" << is;
}
void Widget::testSignal2()
{
CAEEngine *pCAEEngine = new CAEEngine();
AudioProcess *pAudioProcess = new AudioProcess();
// 连接信号
connect(pCAEEngine, CAEEngine::onSignalCAEEnable, pAudioProcess, AudioProcess::onCAEEnableMessage);
connect(pCAEEngine, CAEEngine::onSignalCAEDisable, pAudioProcess, AudioProcess::onCAEDisableMessage);
// 创建一个按钮
QPushButton *pButton = new QPushButton();
pButton->setText("触发信号"); // 设置按钮文本
pButton->setParent(this);
// 按钮点击信号触发 CAEEngine 的 onSignalCAEEnable 和 onSignalCAEDisable 信号
connect(pButton, QPushButton::clicked, pCAEEngine, CAEEngine::onSignalCAEEnable);
connect(pButton, QPushButton::clicked, pCAEEngine, CAEEngine::onSignalCAEDisable);
}
void Widget::testSignal2()
{
CAEEngine *pCAEEngine = new CAEEngine();
AudioProcess *pAudioProcess = new AudioProcess();
// 连接信号
connect(pCAEEngine, CAEEngine::onSignalCAEEnable, pAudioProcess, AudioProcess::onCAEEnableMessage);
connect(pCAEEngine, CAEEngine::onSignalCAEDisable, pAudioProcess, AudioProcess::onCAEDisableMessage);
// 创建一个按钮
QPushButton *pButton = new QPushButton();
pButton->setText("触发信号"); // 设置按钮文本
pButton->setParent(this);
// 按钮点击信号触发 CAEEngine 的 onSignalCAEEnable 和 onSignalCAEDisable 信号
connect(pButton, QPushButton::clicked, pCAEEngine, CAEEngine::onSignalCAEEnable);
connect(pButton, QPushButton::clicked, pCAEEngine, CAEEngine::onSignalCAEDisable);
// 断开 onSignalCAEDisable 信号
disconnect(pButton, QPushButton::clicked, pCAEEngine, CAEEngine::onSignalCAEDisable);
}