Q_INVOKABLE does not expose method to QML - c++

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.

Related

Access an attribute c++ object from QML

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() )

Qt QML Access a QObject by a String

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

Error using Connections in c++ defined QML Type

when using Connections on a c++ defined QML Type, I get the following error:
Cannot assign to non-existent default property.
Following code produces the error:
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQml 2.15
import myextension 1.0
Window {
id:root
width: 640
height: 480
visible: true
Custom
{
Connections{
//target:root
//function onWidthChanged(){console.debug("received")}
}
}
}
customcppclass.h
#ifndef CUSTOMCPPCL_H
#define CUSTOMCPPCLASS_H
#include <QObject>
class Message : public QObject
{
Q_OBJECT
public:
};
#endif // CUSTOMCPPCLASS_H
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "customcppclass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<Message>("myextension", 1, 0, "Custom");
const QUrl url(QStringLiteral("qrc:/main.qml"));
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();
}
Is there a different way to receive the changed signals of properties in a qml extension?
Your error has nothing to do with Connections objects, or your custom C++ class. You will get the same error if you do this:
QtObject {
Item { }
}
Everything in a QML object has to be stored in a property, including child objects. But it doesn't usually look like a property because they use a default property that automatically moves objects into the list of children.
Item {
QtObject { }
}
is effectively the same as:
Item {
data: QtObject {}
}
Where data is the name of the default property that Item uses to hold its child objects.
The problem with your code is that your object is a simple QObject and has no default property and no built-in method for managing children. The easy way to solve your problem is by creating your Connections object as its own property.
Custom {
property Connections myConnections: Connections {
target: root
function onWidthChanged() { console.debug("received") }
}
}
If you wanted to make it more like the way Item manages its children, then you would have to modify your C++ class to maintain a list and add a default property. That's most likely overkill for your needs though.
UPDATE:
The "overkill" solution would look something like this (This is just extracted from QQuickItem source code):
class Message: public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<QObject> data READ data)
Q_CLASSINFO("DefaultProperty", "data")
public:
QQmlListProperty<QObject> data();
static void data_append(QQmlListProperty<QObject> *, QObject *);
static int data_count(QQmlListProperty<QObject> *);
static QObject *data_at(QQmlListProperty<QObject> *, int);
static void data_clear(QQmlListProperty<QObject> *);
};
You can read up on QQmlListProperty or read the QQuickItem source to figure out the implementation of those functions.

Calling method on C++ singleton object in JS yields "TypeError: Property 'foo' of object [object Object] is not a function"

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.

QT Quick Application Window embed C++ object

I have a QT Quick 2.2 ApplicationWindow and I want to use inside this ApplicationWindow a C++ object.
I know QQuickView view, but this work only for objects which are derived from QQuickItem (not for ApplicationWindow).
I also know qmlRegisterType, but this adds only a general C++ class in QML. I want only to have one C++ object (instantiated in C++ code) in the ApplicationWindow.
Is there a possibility to use a C++ Object in a QT Quick 2.2 ApplicationWindow?
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "myclass.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyClass myClass;
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
myclass.h
#include <QObject>
#include <QString>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0) {};
Q_INVOKABLE QString getValue();
};
myclass.cpp
#include "myclass.h"
#include <QString>
QString MyClass::getValue() {
return "42";
}
qrc:///main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
ApplicationWindow {
visible: true
Text {
text: myClass.getValue()
anchors.centerIn: parent
}
}
Thanks
It depends or your purpose. If you want to define QML type from C++, you should do this in your main.cpp for example :
qmlRegisterType< MyClass >("com.example.myclass", 1, 0, "MyClass");
now in your .qml file, first you need to import your newly created data type using import statement :
import com.example.myclass 1.0
and then you can create an item from your own data type:
import QtQuick 2.2
import QtQuick.Controls 1.1
import com.example.myclass 1.0
ApplicationWindow {
visible: true
Text {
text: myClass.getValue()
anchors.centerIn: parent
}
MyClass {
}
}
But you have another solution. You can pass a QObject object from c++ to QML.
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myClass", new MyClass);
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
Now inside of your qml file you can access myClass object.