一、需求来源

对于使用Qt线程,有两种方式,见本人其他文章:https://www.cnblogs.com/judes/p/6884964.html

个人认为QObject::moveToThread方式是最好的,无需死循环判断某个条件是否成立,如此非常消耗CPU【用C++11条件变量可解决】

所以翻遍整个网络也想要找到QML+moveToThread操作线程的方式。

我理想中的工作模式是:

所有工作类【如网络、串口等】继承于QObject,然后moveTothread到某个QThread对象,QML里通过信号与槽的方式控制工作类的开关及数据收发。

无奈,只有此博主提及了一下:https://blog.csdn.net/LHRui_1/article/details/83861142,我最开始的做法与此博主开始一致:

在main.cpp里moveTothread,然后注册这个对象到上下文,再在qml直接访问对象内部。

这样是行不通的,会提示错误:不能访问非当前线程对象..【大致意思是这样】

因为:main.cpp里,工作对象是子线程的【已经moveTothread了】,而engine是主线程的,使用主线程engine注册子线程工作对象到上下文,然后在QML里调用,肯定会出错,具体原因也不是很明白,实在没有相关资料了。

二、解决

还好,放弃baidu搜索,换了bing国际版搜索,看到一篇文章:https://forum.qt.io/topic/62073/qthread-qml

博士非常谦虚,没有讽刺QThread子类方法【因为官方也认可这种方法,自然有人使用。而本人非常相信这是不够“效率”的做法,与使用此方法的朋友争论许久,最后放弃。或许代码界没有答案】,

只声明这是ta自己的opinions。在看了文章之后发现,这就是我想要的效果,并且我相信是最高效正确的做法。

1、工作类work.h

  1. #ifndef WORKER_H
  2. #define WORKER_H
  3.  
  4. #include <QObject>
  5. #include <QDebug>
  6. #include <QThread>
  7.  
  8. class Worker: public QObject {
  9. Q_OBJECT
  10. public:
  11. Worker(QString &data) : mData(data) {}
  12.  
  13. public slots:
  14. void process() {
  15. qDebug() << "Process's Thread : " << QThread::currentThreadId();
  16. mData += "process\n";
  17.  
  18. emit processFinished();
  19. }
  20.  
  21. signals:
  22. void processFinished();
  23.  
  24. private:
  25. QString &mData;
  26. };
  27.  
  28. class WorkerInterface : public QObject {
  29. Q_OBJECT
  30. Q_PROPERTY(QString data READ getData NOTIFY dataChanged)
  31. public:
  32. WorkerInterface() : mWorker(mData) {
  33. mWorker.moveToThread(&mThread);
  34. connect(this, &WorkerInterface::process, &mWorker, &Worker::process);
  35. connect(&mWorker, &Worker::processFinished, [this]{
  36. qDebug() << "ProcessFinished in : " << QThread::currentThreadId();
  37. emit dataChanged();
  38. });
  39.  
  40. mThread.start();
  41. }
  42.  
  43. QString getData() const {
  44. return mData;
  45. }
  46.  
  47. ~WorkerInterface() {
  48. mThread.exit();
  49. mThread.wait();
  50. }
  51.  
  52. signals:
  53. void dataChanged();
  54. void process();
  55.  
  56. private:
  57. QThread mThread;
  58. QString mData;
  59. Worker mWorker;
  60. };
  61.  
  62. #endif // WORKER_H

2、main.cpp

  1. #include <QApplication>
  2. #include <QQmlApplicationEngine>
  3. #include <QQmlContext>
  4. #include <QQmlComponent>
  5. #include "worker.h"
  6.  
  7. int main(int argc, char *argv[])
  8. {
  9. QApplication app(argc, argv);
  10. qmlRegisterType<WorkerInterface>("Workers", , , "Worker");
  11.  
  12. QQmlApplicationEngine engine;
  13.  
  14. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  15.  
  16. qDebug() << "Main thread : " << QThread::currentThreadId();
  17.  
  18. return app.exec();
  19. }

3、main.qml

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3. import Workers 1.0
  4.  
  5. ApplicationWindow {
  6. visible: true
  7. width:
  8. height:
  9. title: qsTr("Hello World")
  10.  
  11. SystemPalette {
  12. id: pal
  13. colorGroup: SystemPalette.Active
  14. }
  15.  
  16. Worker {
  17. id: worker;
  18. }
  19.  
  20. Button {
  21. id: button
  22. text: qsTr("Process")
  23. anchors.centerIn: parent
  24.  
  25. onClicked: worker.process();
  26. }
  27.  
  28. Text {
  29. text: worker.data
  30. anchors.horizontalCenter: parent.horizontalCenter
  31. anchors.top: button.bottom
  32. anchors.topMargin:
  33. }
  34. }

三、剖析

1、定义工作类Worker继承于QObject

2、定义连接QML和C++的中间接口类WorkerInterface继承于QObject

3、在中间类WorkerInterface中声明工作类的对象mWorker

4、在中间类WorkerInterface构造函数中连接信号与槽,mWorker::moveToThread,变量初始化等

5、注册中间类WorkerInterface

6、在QML中实例化中间类,并直接调用其信号控制工作类开启其槽函数

只有工作对象Worker才是被moveToThread的,它的槽函数全是在子线程中运行的。为了避免使用主线程的engin注册此对象【上面已分析出不可行】,所以定义了一个中间类:WorkerInterface,所有中间类是工作在main中的,手动调用其信号【如打开关闭】控制其工作对象对应的槽函数。

四、体会

1、Qt官方只是发布花里胡哨、功能强大的库,至于怎么去用非常灵活,官方有时候也只是建议,没有明确的答案。更多的需要自己去实践、源码剖析,得到适合自己的答案。

2、国内资源是真tm少,特别是国外新出的标准、技术,必要时,还是墙外的世界更精彩。

3、做自己认为对的事。

---------------------------------------------实践-----------------------------------------------------

一、Myudp.h

  1. #ifndef MYUDP_H
  2. #define MYUDP_H
  3.  
  4. #include <QObject>
  5. #include <QUdpSocket>
  6. #include <QThread>
  7. class Myudp : public QObject
  8. {
  9. Q_OBJECT
  10. public:
  11. explicit Myudp(QObject *parent = nullptr);
  12. ~Myudp();
  13. signals:
  14. void rcvdDataSignal(const QByteArray&);
  15. void sendedSignal(const QString&);//发送成功
  16. void getAString(QString str);
  17. private slots:
  18. void initSlot();
  19. void requestSlot();
  20. void sendSlot(const QByteArray&);
  21. void closeSlot();
  22. private:
  23. QUdpSocket* udpClient = nullptr;
  24. const QString localIp="127.0.0.1";
  25. const quint16 localPort=8080;
  26. const QString aimIp="127.0.0.1";
  27. const quint16 aimPort=8888;
  28. private:
  29. };
  30.  
  31. class MyudpInterfase : public QObject
  32. {
  33. Q_OBJECT
  34. Q_PROPERTY(QByteArray dataBa MEMBER dataBa)
  35.  
  36. public:
  37. MyudpInterfase()
  38. {
  39. udp.moveToThread(&udpThread);
  40. QObject::connect(&udp,SIGNAL(rcvdDataSignal(QByteArray)),this,SLOT(dataChangeSlot(QByteArray)));
  41. QObject::connect(this,SIGNAL(initSignal()),&udp,SLOT(initSlot()));
  42. QObject::connect(this,SIGNAL(sendSignal(QByteArray)),&udp,SLOT(sendSlot(QByteArray)));
  43. QObject::connect(this,SIGNAL(closeSiganl()),&udp,SLOT(closeSlot()));
  44. udpThread.start();
  45. }
  46. ~MyudpInterfase()
  47. {
  48. udpThread.quit();
  49. udpThread.wait();
  50. }
  51. private slots:
  52. void dataChangeSlot(const QByteArray& ba)
  53. {
  54. dataBa = ba;
  55. emit dataChangeSignal();
  56. }
  57. signals:
  58. void initSignal();
  59. void sendSignal(const QByteArray&);
  60. void closeSiganl();
  61. void dataChangeSignal();
  62. private:
  63. Myudp udp;
  64. QThread udpThread;
  65.  
  66. QByteArray dataBa;
  67. };
  68.  
  69. #endif // MYUDP_H

1.1、Myudp类就是简单的C++中自定义类,应该最终被moveTothread

1.2、MyudpInterfase是用于QML访问的中间类,所有的信号连接可放在构造函数中,并且定义控制Myudp的信号;注意在构造函数中退出线程

1.3、Q_PROPERTY访问属性,如果没有访问器【READ、WRITE】,则需要加MEMBER来指定这个Q_PROPERTY对应的是控制哪个成员变量。

二、Myudp.cpp

  1. #include "myudp.h"
  2.  
  3. Myudp::Myudp(QObject *parent) : QObject(parent)
  4. {
  5.  
  6. }
  7. Myudp::~Myudp()
  8. {
  9. if(udpClient != nullptr)
  10. {
  11. qDebug() << "内存回收";
  12. delete udpClient;
  13. udpClient = nullptr;
  14. }
  15. }
  16. /***********************************************/
  17. // z 函数名称:初始化
  18. // h 函数作用:NULL
  19. // u 函数参数:NULL
  20. // x 函数返回值:NULL
  21. // y 备注:NULL
  22. /***********************************************/
  23. void Myudp::initSlot()
  24. {
  25. if(udpClient == nullptr)
  26. {
  27. udpClient = new QUdpSocket(this);
  28. udpClient->bind(QHostAddress(localIp),localPort);
  29. QObject::connect(udpClient,SIGNAL(readyRead()),this,SLOT(requestSlot()));
  30. }
  31. }
  32.  
  33. /***********************************************/
  34. // z 函数名称:接收数据
  35. // h 函数作用:NULL
  36. // u 函数参数:NULL
  37. // x 函数返回值:NULL
  38. // y 备注:NULL
  39. /***********************************************/
  40. void Myudp::requestSlot()
  41. {
  42. if(udpClient->pendingDatagramSize() == )
  43. {
  44. return;
  45. }
  46. QByteArray ba;
  47. ba.resize(udpClient->pendingDatagramSize());
  48. QHostAddress tempHost("");
  49. quint16 port = ;
  50. udpClient->readDatagram(ba.data(),udpClient->pendingDatagramSize(),&tempHost,&port);
  51.  
  52. emit rcvdDataSignal(ba);
  53. }
  54.  
  55. /**
  56. *函数名:发送槽函数
  57. *函数参数:NULL
  58. *函数作用:NULL
  59. *函数返回值:NULL
  60. *备注:NULL
  61. */
  62. void Myudp::sendSlot(const QByteArray &info)
  63. {
  64. if(info.size()==udpClient->writeDatagram(info,QHostAddress(aimIp),aimPort))
  65. {
  66. QString str = info.toHex().toUpper();
  67. emit sendedSignal(str);
  68. }
  69. }
  70.  
  71. /*****************************************************************/
  72. //作者:朱小勇
  73. //函数名称:关闭
  74. //函数参数:NULL
  75. //函数返回值:NULL
  76. //函数作用:NULL
  77. //备注:NULL
  78. /*****************************************************************/
  79. void Myudp::closeSlot()
  80. {
  81. udpClient->close();
  82. }

三、main.cpp

  1. #include <QGuiApplication>
  2. #include <QQmlApplicationEngine>
  3. #include <QThread>
  4. #include "myudp.h"
  5. #include <QQuickView>
  6. #include <QQmlContext>
  7. #include <QTimer>
  8. int main(int argc, char *argv[])
  9. {
  10. QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
  11.  
  12. QGuiApplication app(argc, argv);
  13.  
  14. qmlRegisterType<MyudpInterfase>("Myudp.module",,,"Udp");
  15.  
  16. #if 1
  17. QQmlApplicationEngine engine;
  18. engine.load(QUrl(QStringLiteral("qrc:/qml/my.qml")));
  19. if (engine.rootObjects().isEmpty())
  20. return -;
  21. #endif
  22.  
  23. return app.exec();
  24. }

四、my.qml

  1. import QtQuick 2.9
  2. import QtQuick.Window 2.2
  3. import QtQuick.Controls 2.2
  4. import QtQuick.Controls 1.4
  5. import QtGraphicalEffects 1.0
  6. import QtQuick.Layouts 1.3
  7. import Myudp.module 1.0
  8. ApplicationWindow{
  9. id: root
  10. visible: true
  11. width: Screen.width
  12. height: Screen.height
  13. title: qsTr("test")
  14. Component.onCompleted: {
  15. root.visibility = Window.Maximized
  16. }
  17.  
  18. Udp {
  19. id: udp
  20. }
  21.  
  22. TabView {
  23. anchors.fill: parent
  24. Tab {
  25. title: "UDP"
  26. Rectangle {
  27. GroupBox {
  28. id: group1
  29. title: "数据接收"
  30. width: parent.width/
  31. height: parent.height
  32. flat: false
  33. TextEdit{
  34. id: rcvTextEdit
  35. anchors.fill: parent
  36. anchors.margins: 5
  37. }
  38. Connections {
  39. target: udp
  40. onDataChangeSignal: {
  41. rcvTextEdit.append(udp.dataBa.toString().toUpperCase())
  42. }
  43. }
  44. }
  45. GroupBox{
  46. id: group2
  47. title: "数据发送"
  48. width: parent.width/
  49. height: parent.height/
  50. anchors.left: group1.right
  51. flat: false
  52. TextEdit{
  53. id: sendTextEdit
  54. anchors.fill: parent
  55. anchors.margins:
  56. }
  57. }
  58. Rectangle {
  59. width: parent.width/
  60. height: parent.height/
  61. anchors.top: group2.bottom
  62. anchors.left: group1.right
  63. Row {
  64. width: parent.width
  65. height:
  66. spacing:
  67. Button {
  68. text: qsTr("初始化")
  69. onClicked: {
  70. udp.initSignal()
  71. }
  72. }
  73. Button {
  74. text: qsTr("发送")
  75. onClicked: {
  76. udp.sendSignal(sendTextEdit.text)
  77. }
  78. }
  79. Button {
  80. text: qsTr("退出")
  81. onClicked: {
  82. udp.closeSiganl()
  83. Qt.quit()
  84. }
  85. }
  86. }
  87. }
  88. }
  89. }
  90. Tab {
  91. title: "Blue"
  92. Rectangle { color: "blue" }
  93. }
  94. Tab {
  95. title: "Green"
  96. Rectangle { color: "green" }
  97. }
  98. }
  99. }

4.1、注意上面的Connections

由于udp是全局的,而rcvTextEdit是多层控件树下的,所以Connections的位置不能随意写,刚开始我把Connections放在了和udp一个层级,注意这样就访问不到rcvTextEdit了。

QML使用moveToThread线程【QML工程使用C++】的更多相关文章

  1. QML官方系列教程——QML Applications

    附网址:http://qt-project.org/doc/qt-5/qmlapplications.html 假设你对Qt的官方demo感兴趣,能够參考本博客的另一个系列Qt5官方demo解析集 每 ...

  2. QML小例子【QML工程里信号与槽】

    1.效果 代码参考B站视频:https://www.bilibili.com/video/av36584062 功能:点击左边,会发出信号,右边会有个颜色动画,然后计数+1 2.分析: 一共有两个对象 ...

  3. qml: 自定义按钮-- 仿QML自带控件;

    import QtQuick 2.0 Rectangle { id: btn; width:; height:; radius:; border.color: "#A3A3A3"; ...

  4. qml demo分析(threadedanimation-线程动画)

    一.效果预览 使用过qml的同学都知道,使用qml做动画效果是非常简单的,再也不需要像QWidget那样,自己模拟一个动画,费时又费力,往往还达不到效果.今天我们就来分析下qml的两种动画实现方式,如 ...

  5. ubuntu下使用golang、qml与ubuntu sdk开发桌面应用

    ubuntu下使用golang.qml与ubuntu sdk开发桌面应用 (简单示例) 找了很长时间go的gui库,试了gtk,准备试qt的时候发现了这个qml库,试了下很好用. 准备工作 1.Go ...

  6. 解读QML之三

    QML语法 1.QML基本语法 1.1导入声明 导入声明允许客户端告诉QML引擎可以在QML文档中使用哪些模块,JavaScript资源以及组件目录.文档中可以使用的类型依赖于在文档中导入的模块.资源 ...

  7. qml demo分析(threading-线程任务)

    一.关键类说明 qml内置了WorkerScript组件,该组件有一个source属性,可以加载js文件,含有一个名为message的信号,意味着他有一个默认的onMessage槽函数,除此之外他还有 ...

  8. qml demo分析(samegame-拼图游戏)

    一.效果展示 相信大家都玩儿过连连看游戏,而且此款游戏也是闲时一款打发时间的趣事,那么接下来我将分析一款类似的游戏,完全使用qml编写界面,复杂逻辑使用js完成.由于此游戏包含4种游戏模式,因此本篇文 ...

  9. ubuntu下使用golang、qml与ubuntu sdk开发桌面应用 (简单示例)

    找了很长时间go的gui库,试了gtk,准备试qt的时候发现了这个qml库,试了下很好用. ##准备工作 **1.Go 1.2RC1** go的版本应该不能低于这个,我是在1.2RC发布当天升级后发现 ...

随机推荐

  1. 大数据之路week07--day01(HDFS学习,Java代码操作HDFS,将HDFS文件内容存入到Mysql)

    一.HDFS概述 数据量越来越多,在一个操作系统管辖的范围存不下了,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,因此迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统 ...

  2. 《3+1团队》第七次作业:团队项目设计完善&编码

    项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 3+1团队 团队博客地址 https://home.cnblogs.com/u/3-1group ...

  3. unittest(二)框架中的概念与断言

    test case一个 TestCase 的实例就是一个测试用例.什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),实现测试过程的代码(run),以及测试后环境的还原( ...

  4. 清除MSSQL历史记录

    declare @dt datetime select @dt = cast(N'2019-05-21T14:13:45' as datetime) exec msdb.dbo.sp_delete_b ...

  5. myeclipse2018大括号之前会自动加空格

  6. 十一.简单MariaDB数据库的管理

    默认端口:3306 1.安装 ]# yum -y install mariadb-server ]# systemctl restart mariadb 2.基本操作 ]# mysql #进入 ]&g ...

  7. SpringBoot项目中,Redis的初次使用

    1.引入Redis依赖包,在application.yml中配置redis <dependency> <groupId>org.springframework.boot< ...

  8. 使用AwesomeWM作为Mate(Gnome相同) Desktop的窗口管理器

    本文通过MetaWeblog自动发布,原文及更新链接:https://extendswind.top/posts/technical/using_awesomewm_as_wm_of_mate_des ...

  9. 使用jsonpath解析多层嵌套的json响应信息

    Python自带的json库可以把请求转为字典格式, 但在多层嵌套的字典中取值往往要进行多次循环遍历才能取到相应的数据, 如: res_dict = { "code": 0, &q ...

  10. 激活 phpstorm2019.1 win10

    首先添加以下内容到c:\windows\system32\drivers\etc\hosts 文件 0.0.0.0 account.jetbrains.com 0.0.0.0 www.jetbrain ...