I am having a problem understanding how to use a c++ singleton object from qml.
I know that I have to inherit my classes from the QObject class and that I have to expose the properties.
And that in order for them to be usable in the qml I have to do setContextProperty("ClassName", &class name).
However, if we admit that this class contains another object and that I want to be able to use it from the qml, I get errors like "cannot call method name" from undefined object.
Example:
APi.h
class API : public QObject {
Q_OBJECT
Q_PROPERTY(User *user READ getUser WRITE setUser NOTIFY userChanged)
public:
Q_INVOKABLE inline User *getUser() const {
qDebug() << "get called";
return user;
};
inline void setUser(User *new_user) { this->user = new_user; };
signals:
void userChanged();
private:
User *user;
};
User.h
#ifndef USER_H
#define USER_H
#include <QObject>
#include <QString>
class User: public QObject{
Q_OBJECT
public:
Q_INVOKABLE inline QString &getName(){return name;}; // qrc:/main.qml:63: TypeError: Cannot call method 'getName' of undefined
private:
QString name;
};
#endif // USER_H
main.cpp
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQuick3D/qquick3d.h>
#include "API.h"
#include "User.h"
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
API api;
User user;
api.setUser(&user);
engine.rootContext()->setContextProperty("API", &api);
qmlRegisterType<User>("User", 1, 0, "User");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick
import QtQuick3D
import QtQuick.Controls
import QtQuick.Layouts
Window {
Item{
id: test
Component.onCompleted: {
console.log("Completed")
API.getUser() // no error, getUser is called without error
console.log(API.getUser().getName())
}
}
}
What possibilities do I have to access the User object from the qml through API?
You could do any of the following:
Add a public slot in API class:
API.cpp:
QString API::getUserName()
{
return user.getName();
}
main.qml:
Component.onCompleted: console.log( API.getUserName() )
Make User a Q_PROPERTY in API class:
API.h:
Q_PROPERTY( User* user READ user NOTIFY userChanged)
main.qml:
Component.onCompleted: console.log( API.user.getName() )
Register User with QML:
main.cpp:
qmlRegisterType<User>("MyUser", 1, 0, "MyUser");
main.qml:
Component.onCompleted: console.log( API.getUser().getName() )
Related
I have a Model which is exposed to the qml context property and it contains a List of Lists. These Lists are created dynamically and are then exposed to the qml context as well.
Model:
#include <QtCore>
#include <QQmlContext>
class Model : public QObject
{
Q_OBJECT
public:
Model(QQmlContext* rootContext) {
rootContext->setContextProperty(QML_NAME, this);
dataLists.push_back( new DatedValueList(rootContext, "List1", "m"));
names.push_back("List1");
}
QList<DatedValueList*> dataLists;
QString QML_NAME = "Model";
QList<QString> names;
Q_INVOKABLE QList<QString> getListNames() { return names };
};
DatedValueList:
#include <QObject>
#include <QtCore>
#include <QQmlContext>
class DatedValueList: public QObject, public QList<int>
{
Q_OBJECT
public:
DatedValueList(QQmlContext* rootContext, QString i_name, QString i_unit) : name(i_name), unit(i_unit)
{
rootContext->setContextProperty(name, this);
}
QString name; //under this name it is exposed
QString unit;
Q_INVOKABLE QString getName() {return name;}
Q_INVOKABLE QString getUnit() {return unit;}
};
Main:
#include <QtCore>
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
//create Controller w/ thread
QQmlContext* rootContext = engine.rootContext();
Model model(rootContext );
//load QML into engine
engine.load(QUrl(QStringLiteral("qrc:/resource/qml/MainWindow.qml")));
return app.exec();
}
in QML I now want to access one of these DatedValueLists like this. MainWindow.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainview
width: 1800
height: 768
visible: true
flags: Qt.Window
Timer {
interval: 1000 // 1 Hz
running: true
repeat: true
onTriggered: {
console.log( Model.getListNames()[0] ) // Output: List1
console.log( Model.getListNames()[0].getUnit() ) // Not working
}
}
}
The second output line is not working. It is a String and I want to call a function of on it. But how can I cast it or use the String as a qml context id?
As minimal requirement you will have to implement a function that returns the DatedValueList on your Model class:
Q_INVOKABLE DatedValueList getList(const QString& name) { return ... };
But this immediately poses a small challenge, since your data structure does not allow lookup and thus should iterate the whole dataLists member.
I think you are better of providing a QVariantMap from your Model class, you can give a name to each list and also obtain the list itself:
//C++
Q_PROPERTY(QVariantMap listMap ...)
//QML
model.listMap.keys()[0]
model.listMap[model.listMap.keys()[0]].getUnit()
But this can even be a bad decision depending on how you want to shape the rest of your UI, which I'm most sure will not be console.log's only ;-)
Another note, context properties should not have a capital for the first letter. You are likely instantiating a Model inside the console.log call instead of referencing the one instantiate in main
I want to call C++ method from QML, passing parameters to it. As i understood, i should mark class with macro Q_OBJECT and desired public functions with Q_INVOKABLE.
Although i did it, i still get in runtime error
qrc:/main.qml:42: TypeError: Property 'addFile' of object QObject(0xf20e90) is not a function
Here is my .hpp and .cpp files of the class:
lib_controller.hpp
#include <QObject>
#include <QString>
...
class LibController : public QObject{
Q_OBJECT
Q_PROPERTY(decltype(getProgress) progress READ getProgress NOTIFY ProgressChanged)
public:
...
Q_INVOKABLE
void addFile(QString from_name, QString to_name);
...
};
lib_controller.cpp
#include "lib_controller.hpp"
...
void LibController::addFile(QString from_name, QString to_name){
file_manager = new FileManager(from_name.toUtf8().constData(),
to_name.toUtf8().constData());
}
...
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include "lib_controller.hpp"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
// Registration of custom type
qmlRegisterType<LibController>("com.sort.controller", 0, 1, "LibController");
...
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import com.sort.controller 0.1
...
FileDialog {
id: fileDialog_for_load
title: "Load file"
onAccepted: {
fileDialog_for_save.open()
}
}
FileDialog {
id: fileDialog_for_save
title: "Save file"
onAccepted: {
var loadPath = fileDialog_for_load.fileUrl.toString();
loadPath = loadPath.replace(/^(file:\/{2})/,"");
var savePath = fileDialog_for_save.fileUrl.toString();
savePath = savePath.replace(/^(file:\/{2})/,"");
console.log("Save Path:" + savePath)
libController.addFile(loadPath, savePath)
}
}
LibController { id: libController }
What i want, is to call function addFile() to construct file_manager member then it needs to sort new file.
Why is this error happenning? What am I doing wrong?
According to the docs, fileUrl property of FileDialog returns a url type which is equivalent to C++ type QUrl not QString. So you can either:
Edit your C++ method to take two QUrls.
Or in your QML pass .fileUrl.toString().
in your libcontroller constructor you delete your private member instead of initializing it.
I think you are missing a component instancing LibController { id:libController } in your main.qml.
I'm registering a C++ class as a QML singleton, and attempting to call a method on this singleton from JS. I'm getting this error message:
TypeError: Property 'readAll' of object [object Object] is not a function
My code:
main.cpp (just registers a singleton, done by the books):
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "fileio.h"
static QObject* fileIOSingletonTypeProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
FileIO* example = new FileIO();
return example;
}
static void registerQmlTypes()
{
qmlRegisterSingletonType<FileIO>("FileIO", 1, 0, "FileIO", fileIOSingletonTypeProvider);
}
int main(int argc, char *argv[])
{
registerQmlTypes();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
fileio.cpp:
#include "fileio.h"
FileIO::FileIO(QObject *parent) :
QObject(parent)
{
}
void FileIO::readAll()
{
}
fileio.h:
#ifndef FILEIO_H
#define FILEIO_H
#include <QObject>
class FileIO : public QObject
{
Q_OBJECT
public:
explicit FileIO(QObject *parent = 0);
Q_INVOKABLE void readAll();
};
#endif // FILEIO_H
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
import "test.js" as Code
Window {
visible: true
Component.onCompleted: {
Code.func();
}
}
test.js:
.import FileIO 1.0 as FileIO
function func() {
FileIO.readAll();
}
I have found the answer here. Although they refer to importing from QML and I refer to importing from JS, the solution is the same, namely:
Either call the method as FileIO.FileIO.readAll() or remove the as FileIO from the .import statement.
This is not at all apparent from the docs which neither mention the need to double the FileIO qualifier, nor mention the possibility to remove the as FileIO. I've reported a documentation bug to Qt.
I am trying to write code that will pass some data from C++ engine to the Qml scripts via signal, but it looks like that I doing some thing wrong, because when I receive signal in Qml my object don't any method or properties! Look at that code:
Signaller - class that invoke signal:
signaller.h:
class Signaller : public QObject
{
Q_OBJECT
public:
explicit Signaller(QObject *parent = 0);
Q_INVOKABLE void invokeSignal();
signals:
void mysignal(TestClass test);
public slots:
};
signaller.cpp:
Signaller::Signaller(QObject *parent) :
QObject(parent)
{
}
void Signaller::invokeSignal()
{
TestClass s;
emit mysignal(s);
}
TestClass - class that will be passed to Qml engine, and which must have test method in Qml script:
Test.h:
class TestClass : public QObject
{
Q_OBJECT
public:
explicit TestClass(QObject *parent = 0);
TestClass(const TestClass& obj);
~TestClass();
Q_INVOKABLE void test();
signals:
public slots:
};
Q_DECLARE_METATYPE(TestClass)
Test.cpp:
TestClass::TestClass(QObject *parent) :
QObject(parent)
{
qDebug()<<"TestClass::TestClass()";
}
TestClass::TestClass(const TestClass &obj) :
QObject(obj.parent())
{
qDebug()<<"TestClass::TestClass(TestClass &obj)";
}
TestClass::~TestClass()
{
qDebug()<<"TestClass::~TestClass()";
}
void TestClass::test()
{
qDebug()<<"TestClass::test";
}
Those 2 classes also registered in main function:
int main(int argc, char *argv[])
{
// SailfishApp::main() will display "qml/template.qml", if you need more
// control over initialization, you can use:
//
// - SailfishApp::application(int, char *[]) to get the QGuiApplication *
// - SailfishApp::createView() to get a new QQuickView * instance
// - SailfishApp::pathTo(QString) to get a QUrl to a resource file
//
// To display the view, call "show()" (will show fullscreen on device).
qmlRegisterType<TestClass>("Test", 1, 0, "Test");
qmlRegisterType<Signaller>("Signaller", 1, 0, "Signaller");
return SailfishApp::main(argc, argv);
}
That is my Qml file with test code:
import Signaller 1.0
Page {
Signaller {
id: sig
Component.onCompleted: sig.invokeSignal()
onMysignal: {
console.log("signal",typeof test);
console.log(typeof test.test);
}
}
}
And the log:
[D] TestClass::TestClass:6 - TestClass::TestClass()
[D] TestClass::TestClass:12 - TestClass::TestClass(TestClass &obj)
[D] TestClass::TestClass:12 - TestClass::TestClass(TestClass &obj)
[D] onMysignal:41 - signal object
[D] onMysignal:42 - undefined
[D] TestClass::~TestClass:18 - TestClass::~TestClass()
[D] TestClass::~TestClass:18 - TestClass::~TestClass()
As you can see from log, TestClass.test field is empty after passing to the Qml.
What I am doing wrong?
You are passing a QObject derived object by value through the signal/slot system - you should never do this (docs). Create it on the heap and send it's pointer through.
In this case you'll probably want it's lifetime controlled via QML, you can set that by calling QQmlEngine::setObjectOwnership( s, QQmlEngine::JavaScriptOwnership).
QObject should not be used by value in signals and slots. Also, It is a bad idea to implement a copy constructor for a QObject subclass when QObject itself hides its copy constructor.
Thus change your signals to pass pointers to QObject and It will be fine. There is a short and good reference on how to communicate between C++ and Qml
ps: You don't need to register classes which declare the Q_OBJECT macro.
This is an example I made for myself for testing C++ <--> QML interaction:
//File: animal.h
#ifndef ANIMAL_H
#define ANIMAL_H
#include <QObject>
class Animal : public QObject
{
Q_OBJECT
Q_PROPERTY(QString animal_name READ get_animal_name WRITE set_animal_name)
public:
explicit Animal(QObject *parent = 0);
QString get_animal_name();
void set_animal_name(QString p_name);
private:
QString animal_name;
};
#endif // ANIMAL_H
//File: animal.cpp
#include "animal.h"
Animal::Animal(QObject *parent) : QObject(parent)
{
}
void Animal::set_animal_name(QString p_name) {
animal_name=p_name;
}
QString Animal::get_animal_name() {
return animal_name;
}
//File: zoo.h
#ifndef ZOO_H
#define ZOO_H
#include <QObject>
class Animal;
class Zoo : public QObject
{
Q_OBJECT
public:
explicit Zoo(QObject *parent = 0);
Q_INVOKABLE Animal* get_animal_by_index(int index);
Q_INVOKABLE void add_animal(Animal *a);
Q_INVOKABLE void dump_animal_info(Animal *a);
Q_INVOKABLE void emit_signal();
signals:
void some_signal(Animal *animal_object);
public slots:
private:
QList<Animal*> animals;
};
#endif // ZOO_H
//File: zoo.cpp
#include <QDebug>
#include "zoo.h"
#include "animal.h"
Zoo::Zoo(QObject *parent) : QObject(parent)
{
Animal *a;
a=new Animal();
a->set_animal_name("Black Bear");
add_animal(a);
a=new Animal();
a->set_animal_name("Gray Wolf");
add_animal(a);
}
Animal* Zoo::get_animal_by_index(int index) {
return animals.at(index);
}
void Zoo::add_animal(Animal *a) {
animals.append(a);
}
void Zoo::dump_animal_info(Animal *a) {
qWarning() << "animal_name=" << a->get_animal_name();
}
void Zoo::emit_signal() {
Animal *a;
a=animals.at(0);
emit some_signal(a);
}
//File: main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "animal.h"
#include "zoo.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Zoo>("zoo",1,0,"Zoo");
qmlRegisterType<Animal>("zoo.animal",1,0,"Animal");
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
//File: main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import zoo 1.0
import zoo.animal 1.0
ApplicationWindow {
visible: true
width: 640; height: 480; title: qsTr("Zoo")
Zoo {
id: zoopark
}
Column {
Button {
text: "Test C++ <--> QML data exchange by Method"
onClicked: {
var animal=zoopark.get_animal_by_index(1);
zoopark.dump_animal_info(animal);
}
}
Button {
text: "Text C++ <--> QML data exchage by Signal"
onClicked: {
zoopark.emit_signal();
}
}
}
Connections {
target: zoopark
onSome_signal: {
console.log('signal received');
console.log('from qml: animal name=' + animal_object.animal_name)
console.log('dumping animal info from c++:')
zoopark.dump_animal_info(animal_object)
}
}
function process_signal() {
console.log('from qml animal name=' + animal_object.animal_name)
zoopark.dump_animal_info(animal_object)
}
}
I have a small class that is not working properly, and I can't get what is wrong with it. The compiler gives the message:
main.cpp: error: undefined reference to 'CDetails::CDetails()'
This is the snapshot from the code:
//main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <QQmlContext>
#include <QDebug>
class CDetails : public QObject
{ Q_OBJECT
public:
CDetails() {}
~CDetails(void) {}
public slots:
void cppSlot(const QString &msg)
{ qDebug() << "Called the C++ slot with message:" << msg;
}
};
int main(int argc, char *argv[])
{ QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer viewer;
viewer.setMainQmlFile(QStringLiteral("qml/testqml/main.qml"));
viewer.showExpanded();
CDetails *test = new CDetails();
QObject::connect((QObject*)viewer.rootObject(),
SIGNAL(qmlSignal(QString)),test,
SLOT(cppSlot(QString)));
return app.exec();
}
And in main.qml:
import QtQuick 2.0
Rectangle {
id: guide
width: 360
height: 360
signal qmlSignal(string msg)
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
property double scaleFactor: 1.0
property string iconUrl: "image.png"
MouseArea {
anchors.fill: parent
onClicked: {
guide.qmlSignal("Hello from QML")
}
}
}
Update: Thanks for the suggestion on constructor. Now the error is:
error: undefined reference to 'vtable for CDetails'
What is missed here? All suggestions are welcome.
error: undefined reference to 'vtable for CDetails'
What is missed here? All suggestions are welcome.
Seems you are missing the moc include before the main function.
main.cpp
#include <QtGui/QGuiApplication>
#include <QQmlContext>
#include <QDebug>
class CDetails : public QObject
{ Q_OBJECT
public:
CDetails() {}
~CDetails(void) {}
public slots:
void cppSlot(const QString &msg)
{ qDebug() << "Called the C++ slot with message:" << msg;
}
};
#include "main.moc"
int main(int argc, char *argv[])
{ QGuiApplication app(argc, argv);
QQuickView view;
viewer.setMainQmlFile(QStringLiteral("qml/testqml/main.qml"));
viewer.showExpanded();
CDetails *test = new CDetails();
QObject::connect((QObject*)viewer.rootObject(),
SIGNAL(qmlSignal(QString)),test,
SLOT(cppSlot(QString)));
return app.exec();
}
main.pro
...
TEMPLATE = app
TARGET = main
QT += quick
SOURCES += main.cpp
...
Note that, you will also need to add your custom lines that were there before, like dealing with the application viewer, et al.
Alternatively, you could also decouple the class and the main.cpp which means you would put the declaration of the class into a separate header, and then the defintition into a separate source file.
The main.cpp would include the freshly established header, and you would need to make sure that the new header and source file are added to the HEADERS an SOURCES variables in the qmake project file, respectively to get through the moc processing.
You're missing implementations of your constructor and destructor. Quick fix:
class CDetails : public QObject
{ Q_OBJECT
public:
CDetails() {}
~CDetails(void) {}
...
};