从 C++ (Qt5) 向 QML 项目发送信号

Posted

技术标签:

【中文标题】从 C++ (Qt5) 向 QML 项目发送信号【英文标题】:Sending a signal to a QML item from C++ (Qt5) 【发布时间】:2014-04-09 05:48:57 【问题描述】:

我有一个包含以下内容的 QML 文件:

Text 
    id: testData
    onTaskClicked:
        testData.text = task.name
    

关键是这个 taskClicked 信号。它由另一个小部件 (C++) 发出,需要中继到 QML。

这类似于this SO question,只是那里发布的解决方案不起作用(为什么写在下面)。

C++ 代码:

ctxt->setContextProperty(QLatin1Literal("holiday"), m_model);
ctxt->setContextProperty(QLatin1Literal("bgcolor"), color);

view->setResizeMode(QQuickView::SizeRootObjectToView);

auto mainPath = QStandardPaths::locate(QStandardPaths::DataLocation,
                                           QLatin1Literal("taskview.qml"));

view->setSource(QUrl::fromLocalFile(mainPath));

ctxt->setContextProperty(QLatin1Literal("viewer"), m_view);

m_view 是一个QListView 子类,它发出taskClicked(HolidayTask* task) 信号(来自.h 文件):

Q_SIGNALS:
    void taskClicked(HolidayTask* task);

colorm_model 在 QML 中注册并在其他地方使用。信号中的对象已经在 QML 中注册。 view 是我的QQuickView

首先我尝试了上述问题中提出的解决方案:

auto root = view->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData");

connect(m_view, SIGNAL(taskClicked(HolidayTask* task), myElement,
        SLOT(taskClicked(HolidayTask* task);

但是,myElement 始终为空(并且我收到有关不存在插槽的运行时警告)。

如果我尝试将视图(QListView)指针设置为 QML 视图的上下文属性,它仍然不起作用。

在所有情况下,我也得到:

QML Connections: Cannot assign to non-existent property "onTaskClicked"

我在这里可能做错了什么?

编辑澄清一些细节:HolidayTask 是一个自定义 QObject 子类,信号 taskClicked 在 C++ 中定义(在 QListView 子类中)

EDIT2:我们正在接近,但没有雪茄:

auto root = quickView->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData"));

connect(m_view, SIGNAL(taskClicked(HolidayTask*)),
        myElement, SIGNAL(taskClicked(HolidayTask* task)));

Text 
    id: testData
    objectName: "testData"
    signal taskClicked(HolidayTask task)
    onTaskClicked: 
        testData.text = task.name
        console.log("CLICk!")
    

产量

QObject::connect: No such signal QQuickText_QML_0::taskClicked(HolidayTask* task) in /home/lb/Coding/cpp/holiday-planner/src/mainwindow.cpp:178
QObject::connect:  (receiver name: 'testData')

更多细节:HolidayTask,我的自定义 QObject 子类,在代码中注册为

qmlRegisterType<HolidayTask>("HolidayPlanner", 1, 0, "HolidayTask");

带有数据的最小 QML:

 import QtQuick 2.0
 import QtQml 2.2

import HolidayPlanner 1.0

Rectangle 
    id: container
    objectName: "container"
    color: bgcolor


   Text 
        id: testData
        objectName: "testData"
        signal taskClicked(HolidayTask task)
        onTaskClicked: 
            testData.text = task.name
            console.log("CLICK")
        
   


EDIT3:最终的工作代码是(请参阅有关原因的答案)

 connect(m_view, SIGNAL(taskClicked(HolidayPlanner::HolidayTask*)),
        myElement, SIGNAL(taskClicked(HolidayPlanner::HolidayTask*)));

通过使用具有完整命名空间的对象起作用。否则 QML 中的签名将不匹配。

【问题讨论】:

【参考方案1】:

但是,myElement 始终为空(并且我收到关于 一个不存在的插槽)。

您正在尝试根据 id 查找子项,而它是基于 objectName 属性的。您需要将 objectName 属性设置为实际找到它所需的值。

此外,您似乎没有在 QML 文本项中声明信号。我不确定它是自定义 C++ 项还是内置项。不幸的是,您没有共享足够的代码来理解这一点。不管怎样,声明你的信号as per documentation。

因此,试试这段代码:

Text 
    id: testData
    objectName: "testData"
    // ^^^^^^^^^^^^^^^^^^^
    signal taskClicked (HolidayTask task)
    // ^^^^^^^^^^^^^^^^^^^
    onTaskClicked:
        testData.text = task.name
    

一旦完成,您就差不多准备好了。您需要确保将您的 HolidayTask 注册到 QML,并且您还需要更改 main.cpp 中的连接语法,如下所示:

connect(m_view, SIGNAL(taskClicked(HolidayTask* task), myElement, SIGNAL(taskClicked(HolidayTask* task)));

简而言之,您需要以这种方式触发 QML 信号处理程序,而不是通过 SLOT

另外,请注意您的连接语法已损坏,因为它缺少末尾的右括号。这需要修复。

我什至会考虑删除指针分配并为此使用基于值或引用的传递。

【讨论】:

信号来自C++,item是自定义的。我会更新帖子。 @Einar:好的,但是我建议不要称它为Text,因为有这样一个默认的 qml 元素。我建议将其称为更具体的用于您的目的。在这种情况下,您也可以在 C++ 中声明信号,但恕我直言,qml 信号声明仍然简单得多。 :) 哦,对不起,我弄错了——“项目”是指HolidayTaskText 是内置的。很抱歉造成混乱! @Einar:啊,好的,这简化了事情。我认为在 QML 中声明信号是最简单的,恕我直言。 我想通了!签名需要具有完整的名称空间(所有名称都在名称空间下)。我在没有附加命名空间的情况下执行“HolidayTask”,显然这对 QML 来说不是好兆头。我将在问题中发布修改后的代码以跟踪其他人。【参考方案2】:

您可以通过以下方式将信号从 C++ 连接到 QML:

 view->rootContext()->setContextProperty("testData",this);
 QObject::connect(this,SIGNAL(taskClicked(HolidayTask* task)),(QObject *)view->rootObject(),SLOT(onTaskClicked(HolidayTask* task)));

当您的信号命名为taskClicked 时,QML 中的插槽应为onTaskClicked

同样在 QML 中,您应该将对象命名为 testData

objectName: "testData"

【讨论】:

【参考方案3】:
QML Connections: Cannot assign to non-existent property "onTaskClicked"

错误告诉你你的Text项目没有信号taskClicked或属性onTaskClicked! 您需要在文本项中声明一个插槽。为此,您只需声明一个函数:

Text 
   id: testData
   objectName: "testData" // as Laszlo said

   function onTaskClicked( task ) 
       testData.text = task.name;
   

但这也行不通,因为您创建的是SLOT( onTaskClicked(QVariant) ) 而不是SLOT(taskClicked(HolidayTask*))。为了与 QML 交换数据,您需要将信号更改为 SIGNAL(taskClicked(QVariant))

Q_SIGNALS:
    void taskClicked(QVariant task);

然后发出:

emit taskClicked( QVariant::fromValue( task ) );

请记住,为了能够使用HolidayTask,它必须是在qmlRegisterType 注册的QObject

你也可以simply call that qml function。

如果您不能使用 QVariant,您可以在 Text 对象中声明一个信号:

Text 
   id: testData
   objectName: "testData" // as Laszlo said

   signal taskClicked ( HolidayTask task )

   onTaskClicked: 
       testData.text = task.name;
   

然后从 C++ SIGNAL 连接到 qml SIGNAL:

auto root = view->rootObject(); 
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData");
connect(m_view, SIGNAL(taskClicked(HolidayTask*), myElement,
    SIGNAL(taskClicked(HolidayTask*));

【讨论】:

正如我上面写的,这会触发一个不存在的信号运行时错误。 @Einar 应该是精确的SIGNAL(taskClicked(HolidayTask*)) 没有“任务”参数名称。

以上是关于从 C++ (Qt5) 向 QML 项目发送信号的主要内容,如果未能解决你的问题,请参考以下文章

使用 qml 从 qt5 发布 HTTP 请求

如何将信号从一个 qml 发送到另一个

qml 从信号发送列表/数组

如何将一维整数数组从 QML 发送到 C++?

如何从 C++ 端连接到 QML 项目的 onClick 事件

如何从 QML 连接 C++ 对象的破坏信号?