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", 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
    width: 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
    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/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: "數據發送"
                    width: parent.width/2
                    height: parent.height/2
                    anchors.left: group1.right
                    flat: false
                    TextEdit{
                        id: sendTextEdit
                        anchors.fill: parent
                        anchors.margins: 5
                    }
                }
                Rectangle {
                    width: parent.width/2
                    height: parent.height/2
                    anchors.top: group2.bottom
                    anchors.left: group1.right
                    Row {
                        width: 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了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM