• 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
    ";
    
            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", 1, 0, "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
         640
        height: 480
        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: 20
        }
    }

    三、剖析

    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() == 0)
        {
            return;
        }
        QByteArray ba;
        ba.resize(udpClient->pendingDatagramSize());
        QHostAddress tempHost("");
        quint16 port = 0;
        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",1,0,"Udp");
    
    
    #if 1
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/qml/my.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    #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
         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: "数据接收"
                         parent.width/2
                        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: "数据发送"
                         parent.width/2
                        height: parent.height/2
                        anchors.left: group1.right
                        flat: false
                        TextEdit{
                            id: sendTextEdit
                            anchors.fill: parent
                            anchors.margins: 5
                        }
                    }
                    Rectangle {
                         parent.width/2
                        height: parent.height/2
                        anchors.top: group2.bottom
                        anchors.left: group1.right
                        Row {
                             parent.width
                            height: 30
                            spacing: 10
                            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了。

  • 相关阅读:
    Python 之 Django框架( Cookie和Session、Django中间件、AJAX、Django序列化)
    SAP 公司间开票 报错 :0080264464 000000 销售机构 未定义
    C++虚函数、纯虚函数,继承及多态、友元
    postgre 用户权限管理
    mysql 主从搭建
    vue 配置开发线上环境
    基于Ant Design UI框架的React项目
    postgresql数据库报“connections on Unix domain socket "/tmp/.s.PGSQL.5432"?”
    postgres 连接数查看与设置
    修改postgresql密码
  • 原文地址:https://www.cnblogs.com/judes/p/11249300.html
Copyright © 2020-2023  润新知