QML使用moveToThread线程【QML工程使用C++】
一、需求来源
对于使用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
#ifndef WORKER_H
#define WORKER_H #include <QObject>
#include <QDebug>
#include <QThread> class Worker: public QObject {
Q_OBJECT
public:
Worker(QString &data) : mData(data) {} public slots:
void process() {
qDebug() << "Process's Thread : " << QThread::currentThreadId();
mData += "process\n"; emit processFinished();
} signals:
void processFinished(); private:
QString &mData;
}; class WorkerInterface : public QObject {
Q_OBJECT
Q_PROPERTY(QString data READ getData NOTIFY dataChanged)
public:
WorkerInterface() : mWorker(mData) {
mWorker.moveToThread(&mThread);
connect(this, &WorkerInterface::process, &mWorker, &Worker::process);
connect(&mWorker, &Worker::processFinished, [this]{
qDebug() << "ProcessFinished in : " << QThread::currentThreadId();
emit dataChanged();
}); mThread.start();
} QString getData() const {
return mData;
} ~WorkerInterface() {
mThread.exit();
mThread.wait();
} signals:
void dataChanged();
void process(); private:
QThread mThread;
QString mData;
Worker mWorker;
}; #endif // WORKER_H
2、main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlComponent>
#include "worker.h" int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<WorkerInterface>("Workers", , , "Worker"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); qDebug() << "Main thread : " << QThread::currentThreadId(); return app.exec();
}
3、main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import Workers 1.0 ApplicationWindow {
visible: true
width:
height:
title: qsTr("Hello World") SystemPalette {
id: pal
colorGroup: SystemPalette.Active
} Worker {
id: worker;
} Button {
id: button
text: qsTr("Process")
anchors.centerIn: parent onClicked: worker.process();
} Text {
text: worker.data
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: button.bottom
anchors.topMargin:
}
}
三、剖析
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
#ifndef MYUDP_H
#define MYUDP_H #include <QObject>
#include <QUdpSocket>
#include <QThread>
class Myudp : public QObject
{
Q_OBJECT
public:
explicit Myudp(QObject *parent = nullptr);
~Myudp();
signals:
void rcvdDataSignal(const QByteArray&);
void sendedSignal(const QString&);//发送成功
void getAString(QString str);
private slots:
void initSlot();
void requestSlot();
void sendSlot(const QByteArray&);
void closeSlot();
private:
QUdpSocket* udpClient = nullptr;
const QString localIp="127.0.0.1";
const quint16 localPort=8080;
const QString aimIp="127.0.0.1";
const quint16 aimPort=8888;
private:
}; class MyudpInterfase : public QObject
{
Q_OBJECT
Q_PROPERTY(QByteArray dataBa MEMBER dataBa) public:
MyudpInterfase()
{
udp.moveToThread(&udpThread);
QObject::connect(&udp,SIGNAL(rcvdDataSignal(QByteArray)),this,SLOT(dataChangeSlot(QByteArray)));
QObject::connect(this,SIGNAL(initSignal()),&udp,SLOT(initSlot()));
QObject::connect(this,SIGNAL(sendSignal(QByteArray)),&udp,SLOT(sendSlot(QByteArray)));
QObject::connect(this,SIGNAL(closeSiganl()),&udp,SLOT(closeSlot()));
udpThread.start();
}
~MyudpInterfase()
{
udpThread.quit();
udpThread.wait();
}
private slots:
void dataChangeSlot(const QByteArray& ba)
{
dataBa = ba;
emit dataChangeSignal();
}
signals:
void initSignal();
void sendSignal(const QByteArray&);
void closeSiganl();
void dataChangeSignal();
private:
Myudp udp;
QThread udpThread; QByteArray dataBa;
}; #endif // MYUDP_H
1.1、Myudp类就是简单的C++中自定义类,应该最终被moveTothread
1.2、MyudpInterfase是用于QML访问的中间类,所有的信号连接可放在构造函数中,并且定义控制Myudp的信号;注意在构造函数中退出线程
1.3、Q_PROPERTY访问属性,如果没有访问器【READ、WRITE】,则需要加MEMBER来指定这个Q_PROPERTY对应的是控制哪个成员变量。
二、Myudp.cpp
#include "myudp.h" Myudp::Myudp(QObject *parent) : QObject(parent)
{ }
Myudp::~Myudp()
{
if(udpClient != nullptr)
{
qDebug() << "内存回收";
delete udpClient;
udpClient = nullptr;
}
}
/***********************************************/
// z 函数名称:初始化
// h 函数作用:NULL
// u 函数参数:NULL
// x 函数返回值:NULL
// y 备注:NULL
/***********************************************/
void Myudp::initSlot()
{
if(udpClient == nullptr)
{
udpClient = new QUdpSocket(this);
udpClient->bind(QHostAddress(localIp),localPort);
QObject::connect(udpClient,SIGNAL(readyRead()),this,SLOT(requestSlot()));
}
} /***********************************************/
// z 函数名称:接收数据
// h 函数作用:NULL
// u 函数参数:NULL
// x 函数返回值:NULL
// y 备注:NULL
/***********************************************/
void Myudp::requestSlot()
{
if(udpClient->pendingDatagramSize() == )
{
return;
}
QByteArray ba;
ba.resize(udpClient->pendingDatagramSize());
QHostAddress tempHost("");
quint16 port = ;
udpClient->readDatagram(ba.data(),udpClient->pendingDatagramSize(),&tempHost,&port); emit rcvdDataSignal(ba);
} /**
*函数名:发送槽函数
*函数参数:NULL
*函数作用:NULL
*函数返回值:NULL
*备注:NULL
*/
void Myudp::sendSlot(const QByteArray &info)
{
if(info.size()==udpClient->writeDatagram(info,QHostAddress(aimIp),aimPort))
{
QString str = info.toHex().toUpper();
emit sendedSignal(str);
}
} /*****************************************************************/
//作者:朱小勇
//函数名称:关闭
//函数参数:NULL
//函数返回值:NULL
//函数作用:NULL
//备注:NULL
/*****************************************************************/
void Myudp::closeSlot()
{
udpClient->close();
}
三、main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QThread>
#include "myudp.h"
#include <QQuickView>
#include <QQmlContext>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qmlRegisterType<MyudpInterfase>("Myudp.module",,,"Udp"); #if 1
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/qml/my.qml")));
if (engine.rootObjects().isEmpty())
return -;
#endif return app.exec();
}
四、my.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Controls 1.4
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
import Myudp.module 1.0
ApplicationWindow{
id: root
visible: true
width: Screen.width
height: Screen.height
title: qsTr("test")
Component.onCompleted: {
root.visibility = Window.Maximized
} Udp {
id: udp
} TabView {
anchors.fill: parent
Tab {
title: "UDP"
Rectangle {
GroupBox {
id: group1
title: "数据接收"
width: parent.width/
height: parent.height
flat: false
TextEdit{
id: rcvTextEdit
anchors.fill: parent
anchors.margins: 5
}
Connections {
target: udp
onDataChangeSignal: {
rcvTextEdit.append(udp.dataBa.toString().toUpperCase())
}
}
}
GroupBox{
id: group2
title: "数据发送"
width: parent.width/
height: parent.height/
anchors.left: group1.right
flat: false
TextEdit{
id: sendTextEdit
anchors.fill: parent
anchors.margins:
}
}
Rectangle {
width: parent.width/
height: parent.height/
anchors.top: group2.bottom
anchors.left: group1.right
Row {
width: parent.width
height:
spacing:
Button {
text: qsTr("初始化")
onClicked: {
udp.initSignal()
}
}
Button {
text: qsTr("发送")
onClicked: {
udp.sendSignal(sendTextEdit.text)
}
}
Button {
text: qsTr("退出")
onClicked: {
udp.closeSiganl()
Qt.quit()
}
}
}
}
}
}
Tab {
title: "Blue"
Rectangle { color: "blue" }
}
Tab {
title: "Green"
Rectangle { color: "green" }
}
}
}
4.1、注意上面的Connections
由于udp是全局的,而rcvTextEdit是多层控件树下的,所以Connections的位置不能随意写,刚开始我把Connections放在了和udp一个层级,注意这样就访问不到rcvTextEdit了。
QML使用moveToThread线程【QML工程使用C++】的更多相关文章
- QML官方系列教程——QML Applications
附网址:http://qt-project.org/doc/qt-5/qmlapplications.html 假设你对Qt的官方demo感兴趣,能够參考本博客的另一个系列Qt5官方demo解析集 每 ...
- QML小例子【QML工程里信号与槽】
1.效果 代码参考B站视频:https://www.bilibili.com/video/av36584062 功能:点击左边,会发出信号,右边会有个颜色动画,然后计数+1 2.分析: 一共有两个对象 ...
- qml: 自定义按钮-- 仿QML自带控件;
import QtQuick 2.0 Rectangle { id: btn; width:; height:; radius:; border.color: "#A3A3A3"; ...
- qml demo分析(threadedanimation-线程动画)
一.效果预览 使用过qml的同学都知道,使用qml做动画效果是非常简单的,再也不需要像QWidget那样,自己模拟一个动画,费时又费力,往往还达不到效果.今天我们就来分析下qml的两种动画实现方式,如 ...
- ubuntu下使用golang、qml与ubuntu sdk开发桌面应用
ubuntu下使用golang.qml与ubuntu sdk开发桌面应用 (简单示例) 找了很长时间go的gui库,试了gtk,准备试qt的时候发现了这个qml库,试了下很好用. 准备工作 1.Go ...
- 解读QML之三
QML语法 1.QML基本语法 1.1导入声明 导入声明允许客户端告诉QML引擎可以在QML文档中使用哪些模块,JavaScript资源以及组件目录.文档中可以使用的类型依赖于在文档中导入的模块.资源 ...
- qml demo分析(threading-线程任务)
一.关键类说明 qml内置了WorkerScript组件,该组件有一个source属性,可以加载js文件,含有一个名为message的信号,意味着他有一个默认的onMessage槽函数,除此之外他还有 ...
- qml demo分析(samegame-拼图游戏)
一.效果展示 相信大家都玩儿过连连看游戏,而且此款游戏也是闲时一款打发时间的趣事,那么接下来我将分析一款类似的游戏,完全使用qml编写界面,复杂逻辑使用js完成.由于此游戏包含4种游戏模式,因此本篇文 ...
- ubuntu下使用golang、qml与ubuntu sdk开发桌面应用 (简单示例)
找了很长时间go的gui库,试了gtk,准备试qt的时候发现了这个qml库,试了下很好用. ##准备工作 **1.Go 1.2RC1** go的版本应该不能低于这个,我是在1.2RC发布当天升级后发现 ...
随机推荐
- JDK环境变量配置linux
安装前先查看是否安装过jdk如果安装过则 卸载 1. 确定JDK的版本: rpm -qa | grep jdk rpm -qa | grep gcj 可能的结果是: libgcj-4.1.2-42.e ...
- 前端学习笔记--CSS布局--float定位
1.float属性 box1向右移动,box2顶替了box1的位置,box3顶替了box2的位置. 2.clear属性 案例: 一列三行布局: <!DOCTYPE html> <ht ...
- unittest(二)框架中的概念与断言
test case一个 TestCase 的实例就是一个测试用例.什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),实现测试过程的代码(run),以及测试后环境的还原( ...
- Git报错:Permission denied (publickey)
Git在克隆的时候报错.Permission denied (publickey). 报错 Permission denied (publickey) 具体如下: 原因:没有将自己的电脑的SSH ke ...
- 开发第一个maven示例
mavenDemo目录如下: 在webapp下新建index.jsp文件 浏览器访问:http://localhost:8080/mavenDemo/index.jsp
- SIGAI机器学习第四集 基本概念
大纲: 算法分类有监督学习与无监督学习分类问题与回归问题生成模型与判别模型强化学习评价指标准确率与回归误差ROC曲线交叉验证模型选择过拟合与欠拟合偏差与方差正则化 半监督学习归类到有监督学习中去. 有 ...
- mysql数据库中锁机制的详细介绍
悲观锁与乐观锁: 悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据库里边就用到了很多这 ...
- SQL SERVER PIVOT使用
参照这个网址介绍 http://www.cnblogs.com/lwhkdash/archive/2012/06/26/2562979.html 一般SQL Server的函数都会识别为紫色,可是PI ...
- webservice的soap
1.soap的定义: 2.使用TCP/IP Monitor监视Soap协议 eclipse工具,show view-->other-->debug-->TCP/IP Monitor ...
- Python多线程笔记(三),queue模块
尽管在Python中可以使用各种锁和同步原语的组合编写非常传统的多线程程序,但有一种首推的编程方式要优于其他所有编程方式即将多线程程序组织为多个独立人物的集合,这些任务之间通过消息队列进行通信 que ...