QThread 如何从它自己的线程发送一个带有枚举的信号作为 QML 消费的参数?

How can a QThread send a signal from its own thread with an enum as an argument for consumption in QML?

在下面的代码中,如果信号 errorHappened 是从主线程发出的,它可以正常工作。但是,如果它是从 QThread 线程发出的,则会失败并出现以下错误:

QObject::connect: Cannot queue arguments of type 'ErrorCode'
(Make sure 'ErrorCode' is registered using qRegisterMetaType().)

有没有办法可以从QThread线程中成功发出信号?如果是,怎么做?

Full code in this Gist

MyClass.h

#import <QThread>
#import <atomic>

class MyClass : public QThread
{
    Q_OBJECT

public:
    explicit MyClass(QObject *parent = Q_NULLPTR);
    virtual ~MyClass() override;

    enum ErrorCode {
        ErrorA,
        ErrorB,
        ErrorC
    };
    Q_ENUM(ErrorCode)

signals:
    void errorHappened(ErrorCode errorCode);

public slots:
    void mainThreadError();
    void otherThreadError();

private:
    std::atomic<bool> m_running;
    std::atomic<bool> m_signalStop;
    std::atomic<bool> m_signalError;

    void run() override;
    void stop();
};

MyClass.cpp

#include "MyClass.h"

MyClass::MyClass(QObject *parent)
    : QThread(parent)
{
    start();
}

MyClass::~MyClass()
{
    stop();
}

void MyClass::mainThreadError()
{
    emit errorHappened(ErrorCode::ErrorA);
}

void MyClass::otherThreadError()
{
    m_signalError = true;
}

void MyClass::run()
{
    m_running = true;

    while (!m_signalStop) {
        if (m_signalError) {
            emit errorHappened(ErrorCode::ErrorA);
            m_signalError = false;
        }
        msleep(1);
    }

    m_running = false;
    m_signalStop = false;
}

void MyClass::stop()
{
    if (m_running) {
        m_signalStop = true;
        wait();
    }
}

main.cpp

#include <QGuiApplication>
#include <QQuickView>
#include "MyClass.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQuickView *view = new QQuickView();

    qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");

    view->setSource((QUrl(QStringLiteral("qrc:/main.qml"))));
    view->create();
    view->show();

    return app.exec();
}

main.qml

import QtQuick 2.12
import QtQuick.Controls 2.5
import MyClass 1.0

Rectangle {
    id: root

    width: 800
    height: 600
    focus: true

    MyClass {
        id: tester
        onErrorHappened: {
            var s
            switch (errorCode) {
            case MyClass.ErrorA:
                s = "Error A happened"
                break
            }
            console.log(s)
        }
    }

    Row {
        spacing: 30

        Button {
            id: mainThreadButton

            enabled: !tester.testRunning
            text: "Test on main thread"
            onClicked: tester.mainThreadError()
        }

        Button {
            id: otherThreadButton

            enabled: !tester.testRunning
            text: "Test on other thread"
            onClicked: tester.otherThreadError()
        }
    }
}

您的类型 "ErrorCode" 仅在线程中定义(而不是在主线程中)

  1. 对于用户定义的类型,您可以使用(阅读此 https://doc.qt.io/qt-5/qmetatype.html

    Q_DECLARE_METATYPE(错误码);

  2. 对参数使用标准类型(int、QMap、QString....)

qmlRegisterType 使 QObject (MyClass) class 可在 QML 中访问(Q_PROPERTY、Q_ENUM、Q_SIGNAL、Q_SLOT、Q_INVOKABLE等),但不允许在线程之间传输数据,对于这种情况,必须使用 qRegisterMetaType<MyClass::ErrorCode>("ErrorCode"):

注册

main.cpp

#include <QGuiApplication>
#include <QQuickView>
#include "MyClass.h"

static void registerTypes(){
    <b>qRegisterMetaType<MyClass::ErrorCode>("ErrorCode");</b>
    qmlRegisterType<MyClass>("MyClass", 1, 0, "MyClass");
}

Q_COREAPP_STARTUP_FUNCTION(registerTypes)

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQuickView view;
    view.setSource((QUrl(QStringLiteral("qrc:/main.qml"))));
    view.show();
    return app.exec();
}