exposing C++ attributes (missing a step)
-
Hi all -
After an 18-month hiatus, I'm returning to QML-land...currently trying scrape the rust off my memory.
I'm trying to connect a C++ object to a QML display. I'm roughly following the instructions here with minor modifications (I have a QQmlApplicationEngine instead of a QQuickView), but I must be missing a step, because I'm getting a runtime error.
Here's a snippet from my main.cpp code:
QQmlApplicationEngine engine; Clock clock; const QUrl url(u"qrc:/nga/main.qml"_qs); engine.rootContext()->setContextProperty("clock", &clock); engine.load(url);
Clock.h:
class Clock : public QObject { Q_OBJECT Q_PROPERTY(QString m_timeStr READ time WRITE setTime NOTIFY timeChanged) public: void setTime(const QString &time); QString time() { return m_timeStr; } private: QString m_timeStr; ...
and my main.qml:
import (a bunch of stuff) ApplicationWindow { Text { text: clock.time } ...
I'm getting a runtime error "TypeError: Cannot read property 'time' of null." Could someone please tell me what I left out?
Thanks...
-
@mzimmers said in exposing C++ attributes (missing a step):
Q_PROPERTY(QString m_timeStr READ time WRITE setTime NOTIFY timeChanged)
I think this should be:
Q_PROPERTY(QString time READ time WRITE setTime NOTIFY timeChanged)But it does not explain why "clock" is null.
-
@mzimmers said in exposing C++ attributes (missing a step):
Q_PROPERTY(QString m_timeStr READ time WRITE setTime NOTIFY timeChanged)
I think this should be:
Q_PROPERTY(QString time READ time WRITE setTime NOTIFY timeChanged)But it does not explain why "clock" is null.
-
Hey, I tried your setup in a sample project (Qt 5.15) and it works just fine:
// Clock.hpp #pragma once #include <QObject> class Clock : public QObject { Q_OBJECT Q_PROPERTY(QString m_timeStr READ time WRITE setTime CONSTANT) public: void setTime(const QString &time) { m_timeStr = time; } QString time() { return m_timeStr; } private: QString m_timeStr = "test"; };
// main.cpp int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; Clock clock; const QUrl url(QStringLiteral("qrc:/main.qml")); engine.rootContext()->setContextProperty("clock", &clock); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
// main.qml ApplicationWindow { id: root visible: true width: 1200 height: 800 Component.onCompleted: console.log(clock.m_timeStr) }
(This prints "test")
I am sure we could help you better, if you'd provide a reproducible example
-
Hey, I tried your setup in a sample project (Qt 5.15) and it works just fine:
// Clock.hpp #pragma once #include <QObject> class Clock : public QObject { Q_OBJECT Q_PROPERTY(QString m_timeStr READ time WRITE setTime CONSTANT) public: void setTime(const QString &time) { m_timeStr = time; } QString time() { return m_timeStr; } private: QString m_timeStr = "test"; };
// main.cpp int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; Clock clock; const QUrl url(QStringLiteral("qrc:/main.qml")); engine.rootContext()->setContextProperty("clock", &clock); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
// main.qml ApplicationWindow { id: root visible: true width: 1200 height: 800 Component.onCompleted: console.log(clock.m_timeStr) }
(This prints "test")
I am sure we could help you better, if you'd provide a reproducible example
@Creaperdown normally my questions are easy enough for this audience that snippets reveal my error, but evidently not so in this case. So, here's the whole thing:
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "clock.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(u"qrc:/nga/main.qml"_qs); Clock clock; engine.rootContext()->setContextProperty("clock", &clock); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
clock.h
#ifndef CLOCK_H #define CLOCK_H #include <QObject> #include <QTime> #include <QTimer> class Clock : public QObject { Q_OBJECT Q_PROPERTY(QString time MEMBER m_timeStr READ time WRITE setTime NOTIFY timeChanged) public: explicit Clock(QObject *parent = nullptr); void setTime(const QString &time); QString time() { return m_timeStr; } signals: void timeChanged(QString time); public slots: void update(); private: QString m_timeStr; QTime m_time; QTimer m_timer; }; #endif // CLOCK_H
clock.cpp
#include <QDebug> #include "clock.h" Clock::Clock(QObject *parent) : QObject{parent} { connect(&m_timer, &QTimer::timeout, this, &Clock::update); m_timer.start(1000); m_timeStr = "uninitialized"; } void Clock::setTime(const QString &time) { if (time != m_timeStr) { m_timeStr = time; emit timeChanged(m_timeStr); } } void Clock::update() { QString l_timeStr; m_time = QTime::currentTime(); l_timeStr = m_time.toString("hh:mm:ss"); if (l_timeStr != m_timeStr) { m_timeStr = l_timeStr; qDebug() << "m_timeStr updated to" << m_timeStr; } }
and main.qml:
import QtQml 2.2 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.15 import QtQuick.Layouts 1.15 ApplicationWindow { visible: true width: 800 height: 480 Mytoolbar { // ignore this id: toolBar } Text { anchors { top: toolBar.bottom } text: clock.time } }
Thanks...
-
@Creaperdown normally my questions are easy enough for this audience that snippets reveal my error, but evidently not so in this case. So, here's the whole thing:
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "clock.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(u"qrc:/nga/main.qml"_qs); Clock clock; engine.rootContext()->setContextProperty("clock", &clock); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
clock.h
#ifndef CLOCK_H #define CLOCK_H #include <QObject> #include <QTime> #include <QTimer> class Clock : public QObject { Q_OBJECT Q_PROPERTY(QString time MEMBER m_timeStr READ time WRITE setTime NOTIFY timeChanged) public: explicit Clock(QObject *parent = nullptr); void setTime(const QString &time); QString time() { return m_timeStr; } signals: void timeChanged(QString time); public slots: void update(); private: QString m_timeStr; QTime m_time; QTimer m_timer; }; #endif // CLOCK_H
clock.cpp
#include <QDebug> #include "clock.h" Clock::Clock(QObject *parent) : QObject{parent} { connect(&m_timer, &QTimer::timeout, this, &Clock::update); m_timer.start(1000); m_timeStr = "uninitialized"; } void Clock::setTime(const QString &time) { if (time != m_timeStr) { m_timeStr = time; emit timeChanged(m_timeStr); } } void Clock::update() { QString l_timeStr; m_time = QTime::currentTime(); l_timeStr = m_time.toString("hh:mm:ss"); if (l_timeStr != m_timeStr) { m_timeStr = l_timeStr; qDebug() << "m_timeStr updated to" << m_timeStr; } }
and main.qml:
import QtQml 2.2 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.15 import QtQuick.Layouts 1.15 ApplicationWindow { visible: true width: 800 height: 480 Mytoolbar { // ignore this id: toolBar } Text { anchors { top: toolBar.bottom } text: clock.time } }
Thanks...
@mzimmers said in exposing C++ attributes (missing a step):
timeChanged
you never call setTime, so the changed signal is never emitted -> ui never updates.
the
Cannot read property 'time' of null
is because of the stack FIFO, create the Clock instance before the GuiApplication instance and that too will go away :D -
This post is deleted!
-
@mzimmers said in exposing C++ attributes (missing a step):
timeChanged
you never call setTime, so the changed signal is never emitted -> ui never updates.
the
Cannot read property 'time' of null
is because of the stack FIFO, create the Clock instance before the GuiApplication instance and that too will go away :D@J-Hilk that was it (sometimes the most obvious omissions are the hardest to find).
clock.cpp:
Clock::Clock(QObject *parent) : QObject{parent} { connect(&m_timer, &QTimer::timeout, this, &Clock::update); m_timeStr = "00:00 AM"; } void Clock::setTime(const QString &time) { if (time != m_timeStr) { m_timeStr = time; emit timeChanged(m_timeStr); } } void Clock::update() { QString l_timeStr; m_time = QTime::currentTime(); l_timeStr = m_time.toString("hh:mm AP"); setTime(l_timeStr); }
I had to move the m_timer.start() out of the c'tor, because I got this runtime error:
QObject::startTimer: Timers can only be used with threads started with QThread
So I created a start() function and I call it from main.cpp. Works like a charm now. Thanks!