介绍说明

学习 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即可。

工程代码

将会以此为基础来做各种各样的实验

main.cpp

#include "widget.h"
#include <QApplication> int main(int argc, char *argv[])
{
// 应用程序对象, 有且仅有一个
QApplication a(argc, argv); // 窗口对象
Widget w; // 默认不会显示, 必须调用 show 方法显示窗口
w.show(); // 进入消息循环
return a.exec();
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H #include <QWidget> class Widget : public QWidget
{
Q_OBJECT public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H

widget.cpp

 #include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
} Widget::~Widget()
{
}

按钮实现

一个较为直观的按钮使用和按钮事件响应的方法

实现源码

widget.cpp

#include <QPushButton>

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信号槽

QT 信号槽是一个很有意思的机制,它提供一种对象与对象之间完全解耦的通讯机制。

下面是以 CAEEngine 来作为信号源,AudioProcess 作为接受源和提供信号处理方法。

1. 自定义信号

caeengine.h

#ifndef CAEENGINE_H
#define CAEENGINE_H #include <QObject>
#include <thread>
#include <string> 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

caeengine.cpp

#include <QDebug>
#include <QDateTime>
#include <synchapi.h>
#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. 自定义槽

audioprocess.h

#ifndef AUDIOPROCESS_H
#define AUDIOPROCESS_H #include <QObject>
#include <string> 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

audioprocess.cpp

#include <QDebug>
#include <QDateTime>
#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. 使用方法

widget.cpp

#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 的信号槽是异步机制,不是直接的函数回调方式。

在信号处理函数中延时实验

widget.cpp(删除其他信号,方便观察打印信息)


void Widget::testSignal()
{
CAEEngine *pCAEEngine = new CAEEngine();
AudioProcess *pAudioProcess = new AudioProcess(); // 方式一: 没有函数重载的方式
connect(pCAEEngine, CAEEngine::onSignalLoadASRCommand, pAudioProcess, AudioProcess::onLocalASRCommandMessage); // 开始启动线程进行周期性触发信号
pCAEEngine->test(); }

实现效果

一开始程序界面处于无响应状态

输出的调试消息(注意打印的时间):

还没有看相应的源代码,因此根据实验现象大胆推测如下几点:

  1. QT 的信号处理应该是基于 UI 主消息循环中处理
  2. QT 的信号发送不受槽函数的阻塞影响,应该有一个栈或者队列来存储这些信号
  3. QT 的 UI 线程应该是定期或者被触发的方式从栈或者队列中取出信号后调用对于的槽函数。

所以,耗时的动作应该有子线程进行,槽函数通过唤醒子线程的方式来做,就不会影响 GUI。

信号如果给出具体实现会怎么样?

会编译报多重定义的错误:

信号触发信号

caeengine.h

#ifndef CAEENGINE_H
#define CAEENGINE_H #include <QObject>
#include <thread>
#include <string> 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

audioprocess.cpp

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;
}

widget.cpp

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);
}

实现效果

也可以断开信号

widget.cpp

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);
}

总结一下

  1. 可以信号与信号建立连接关系
  2. 可以信号与槽建立连接关系
  3. 可以一个信号与多个信号建立连接关系
  4. 可以一个信号与多个槽建立连接关系
  5. 信号和槽函数的参数必须一一对于
  6. 信号参数可以比槽函数的参数个数多
  7. 信号只能被声明而不能被定义,且需要在 signals: 关键字之下,由 emi 触发。
  8. 槽函数被声明后需要被定义,信号或槽函数如果有重载则需要通过类型强转的方式连接。
  9. 耗时的动作应该有子线程进行,槽函数通过唤醒子线程的方式来做,否则会导致 GUI 无响应。
  10. 信号或槽函数的参数类型需要是 QT 已经识别的类型,如果使用自定义或者 std 的类型需要注册,否则信号连接将会失败,并且只在运行时才会提示(检查 connect() 返回值没有用)。如下面的图如果没有:qRegisterMetaTypestd::string(“std::string”); 将会报错:

【学习笔记】QT从入门到实战完整版(按钮和信号槽)(1)的更多相关文章

  1. 2579页阿里P8Android学习笔记在互联网上火了,完整版开放下载

    笔记作者:来自于阿里P8级大神: Mark 笔记特点:条理清晰,理论+实战+源码,含图像化表示更加易懂. 内容概要:Android 相关,性能优化,Java 相关,Kotlin 相关,网络相关,插件化 ...

  2. Hadoop学习笔记(1) ——菜鸟入门

    Hadoop学习笔记(1) ——菜鸟入门 Hadoop是什么?先问一下百度吧: [百度百科]一个分布式系统基础架构,由Apache基金会所开发.用户可以在不了解分布式底层细节的情况下,开发分布式程序. ...

  3. iOS学习笔记-地图MapKit入门

    代码地址如下:http://www.demodashi.com/demo/11682.html 这篇文章还是翻译自raywenderlich,用Objective-C改写了代码.没有逐字翻译,如有错漏 ...

  4. tensorflow学习笔记二:入门基础 好教程 可用

    http://www.cnblogs.com/denny402/p/5852083.html tensorflow学习笔记二:入门基础   TensorFlow用张量这种数据结构来表示所有的数据.用一 ...

  5. 电子书下载:Delphi XE 5 移动开发入门手册(完整版)

    更多电子书请到: http://maxwoods.400gb.com 下载:Delphi XE5移动开发入门手册(完整版)

  6. amazeui学习笔记--js插件(UI增强2)--按钮交互Button

    amazeui学习笔记--js插件(UI增强2)--按钮交互Button 一.总结 1.按钮loading状态: <button type="button" class=&q ...

  7. spark学习笔记总结-spark入门资料精化

    Spark学习笔记 Spark简介 spark 可以很容易和yarn结合,直接调用HDFS.Hbase上面的数据,和hadoop结合.配置很容易. spark发展迅猛,框架比hadoop更加灵活实用. ...

  8. Android学习笔记(二十一)——实战:程序数据共享

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 我们继续在Database项目的基础上继续开发,通过内容提供器来给它加入外部访问接口.首先将 MyDataba ...

  9. Android学习笔记(十五)——实战:强制下线

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 实现强制下线功能的思路也比较简单,只需要在界面上弹出一个对话框, 让用户无法进行任何其他操作, 必须要点击对话 ...

  10. Android学习笔记(十二)——实战:制作一个聊天界面

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 运用简单的布局知识,我们可以来尝试制作一个聊天界面. 一.制作 Nine-Patch 图片 : Nine-Pa ...

随机推荐

  1. JVM学习笔记——内存结构篇

    JVM学习笔记--内存结构篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存结构部分 我们会分为以下几部分进行介绍: JVM整体介绍 程序计数器 虚拟机栈 本地方法栈 堆 方法 ...

  2. CSP2022游记

    第一次几乎完全没有准备的比赛 也是倒数第二场比赛 Day -1 上了一天文化课,晚上还有强基班. 强基班上完之后来机房写了几个板子就开始颓废了 基本上就抱着摆烂的心态 不过是第一次在学校拿到手机 还在 ...

  3. Codeforces Round #786 (Div. 3) 补题记录

    小结: A,B,F 切,C 没写 1ll 对照样例才发现,E,G 对照样例过,D 对照样例+看了其他人代码(主要急于看后面的题,能调出来的但偷懒了. CF1674A Number Transforma ...

  4. Perl printf 函数

    转载 Perl printf 函数

  5. Perl读取Excel中的数据

    #!usr/bin/perl -W use strict; use Spreadsheet::ParseExcel;#PERL的Spreadsheet::ParseExcel模块支持Excel的读操作 ...

  6. linux子网掩码修改记录

    1.输入密码进入linux,并且进入root2.输入ifconfig.返回网卡信息,释:其中eno1为当前以太网名称.Inet IP/子网掩码位置数 Bcast广播地址 或者mask子网掩码3.修改子 ...

  7. 树莓派编译opencv4

    前言 我用的是 树莓派3b 编译的 opencv4.1.0,如果不想编译可以直接下载我编译好的. 下载地址 直接 make install,或者按照我后续步骤复制动态链接库. 准备 需要调节虚拟内存大 ...

  8. Kettle基础及快速入门

    (一)概述 1.ETL ETL(Extract-Transform-Load的缩写,即数据抽取.转换.装载的过程) ETL工具:Sqoop,DataX,Kettle,Talend等 2.Kettle介 ...

  9. 【离线数仓】Day03-系统业务数据仓库:数仓表概念、搭建、数据导入、数据可视化、Azkaban全调度、拉链表的使用

    一.电商业务与数据结构简介 1.业务流程 2.常识:SKU/SPU SKU=Stock Keeping Unit(库存量基本单位).现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU号. ...

  10. Linux开发板连接WPA加密的AP路由器

    Linux目前有两种方法配置网络: wireless-tools wpa_supplicant iw支持的驱动较多,但只支持WEP加密:wpa_supplicant有部分驱动支持不完善,但支持WEP. ...