Qt QML Access a QObject by a String - c++

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

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

Q_INVOKABLE does not expose method to QML

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.

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/QML c++ Program crash on access a QList from QML

I have 2 classes for datahandling (CGameList and CGame).
I define one GameList (_gamelist) object in qml to work with it.
I have a Listviews with show the the games from this GameList (editGames_open()).
If I click on one entry of this list, it open a new list with a detailed view of this game (editGame_open(index)).
This work works like expected.
Now my problem:
If I go back to the list and try open it again, my program crash (not every time, sometimes its work 20x times).
The crash appear after the call of getGame.
If I use the debugger I can see my CGameList-object looks fine(data a correct + item in my QList are correct), but after this the program crash with a Segmentation Fault.
The callstack show only QQMlData::wasDeleted as last entry.
I think the problem is, that my object is delete, but I cant find this.
I had try to change my QList from QList _games to QList* _games but without success.
One other thing(I think its the same problem):
Sometimes getGame give back a NULL-pointer(although the game is in the list, but the data are wrong).
cgamelist.h
#ifndef CGAMELIST_H
#define CGAMELIST_H
#include <QObject>
#include <QList>
#include <qfile.h>
#include <QTextStream>
#include <QDir>
#include <QStandardPaths>
#include <QDateTime>
#include <cgame.h>
class CGameList : public QObject
{
Q_OBJECT
Q_PROPERTY(int itemCount READ getItemCount)
public:
CGameList(QObject *parent = 0);
Q_INVOKABLE bool addGame(QString name,int layout);
Q_INVOKABLE int getItemCount() const;
Q_INVOKABLE void saveGame(int index);
Q_INVOKABLE void loadGames(bool force=true);
Q_INVOKABLE CGame* getGame(int i) const;
Q_INVOKABLE QString getGamename(int i) const;
Q_INVOKABLE QString getGamedate(int i) const;
Q_INVOKABLE void delGame(int i);
private:
QList<CGame*>* _games;
};
#endif // CGAMELIST_H
cgamelist.cpp
CGameList::CGameList(QObject *parent) : QObject(parent)
{
_games=new QList<CGame*>();
_games->clear();
}
...
CGame* CGameList::getGame(int i) const
{
/* CGame*g=new CGame();
g->setGamename("test");
return g;*/
try
{
return _games->at(i);
}
catch(...)
{
return NULL;
}
}
...
cgame.h*
#ifndef CGAME_H
#define CGAME_H
#include <QObject>
#include <QString>
#include <QDateTime>
class CGame : public QObject
{
Q_OBJECT
Q_PROPERTY(QString gamename READ getGamename WRITE setGamename)
Q_PROPERTY(int itemCount READ getItemCount)
Q_PROPERTY(int layout READ getLayout WRITE setLayout)
Q_PROPERTY(int duration READ getDuration WRITE setDuration)
Q_PROPERTY(QDateTime date READ getDate WRITE setDate)
public:
CGame(QObject *parent = 0);
Q_INVOKABLE QString getGamename() const;
Q_INVOKABLE void setGamename(QString name);
Q_INVOKABLE int getItemCount() const;
Q_INVOKABLE int getLayout() const;
Q_INVOKABLE void setLayout(int layout);
Q_INVOKABLE int getDuration() const;
Q_INVOKABLE void setDuration(int duration);
Q_INVOKABLE QDateTime getDate() const;
Q_INVOKABLE void setDate(QDateTime date);
Q_INVOKABLE QString getEvent(int i) const;
Q_INVOKABLE void addEvent(QString ename,int time,int sec,int duration);
private:
QString _name;
int _layout;
int _duration;
QList<QString>* _events;
QDateTime _date;
};
#endif // CGAME_H
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "clayoutlist.h"
#include "clayout.h"
#include "clayoutitem.h"
#include "cgame.h"
#include "cgamelist.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
//QApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<CLayoutList>("STSP.Tag",1,0,"TagLayoutList");
qmlRegisterType<CLayout>("STSP.Tag",1,0,"TagLayout");
qmlRegisterType<CLayoutItem>("STSP.Tag",1,0,"TagLayoutItem");
qmlRegisterType<CGame>("STSP.Tag",1,0,"Game");
qmlRegisterType<CGameList>("STSP.Tag",1,0,"GameList");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.2
import STSP.Tag 1.0
ApplicationWindow {
TagLayoutList {
id: _layoutlist
}
GameList
{
id:_gamelist
}
visible: true
MainForm {
id: mainform
anchors.fill: parent
}
MessageDialog{
id:info
}
...
function editGames_open()
{
_gamelist.loadGames()
mainform.p_gameslistmodel.clear()
var i
for (i = 0; i < _gamelist.getItemCount(); i++) {
mainform.p_gameslistmodel.append({
name: _gamelist.getGamename(i),
date: _gamelist.getGamedate(i),
gameindex: i
})
}
mainform.p_editgames.delmode=false
mainform.p_editgames.visible = true
mainform.p_startmenu.visible=false
}
function editGame_open(index)
{
mainform.p_eventlistmodel.clear()
var game={}
try
{
game=_gamelist.getGame(index)
}
catch(exc)
{
console.log("Serious Error 2 "+exc)
return
}
if(game==null)
{
console.log("Reload Games")
_gamelist.loadGames()
game=_gamelist.getGame(index)
}
if(game==null)
{
console.log("Error Game not found")
return
}
var i
var event
var events
var t,s,m,h
for(i=0;i<game.getItemCount();i++)
{
event=game.getEvent(i).split('#')[1]
//console.log(event)
events=event.split(',')
t=events[1]
s=t%60
t=(t-s)/60
m=t%60
h=(t-m)/60
mainform.p_eventlistmodel.append({
name: events[0],
time: h+":"+(m<10?"0":"")+m+":"+(s<10?"0":"")+s,
eventindex: i
})
}
mainform.p_editgame.gname=game.getGamename()
t=game.getDuration()
s=t%60
t=(t-s)/60
m=t%60
h=(t-m)/60
mainform.p_editgame.gtime=h+":"+(m<10?"0":"")+m+":"+(s<10?"0":"")+s
mainform.p_editgames.delmode=false
mainform.p_editgames.visible=false
mainform.p_editgame.visible=true
}
function editGame_back()
{
mainform.p_editgame.visible=false
mainform.p_editgames.visible=true
}
It's hard to say without being able to debug your program, but it sounds like QML is taking ownership of your CGame object:
When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object, unless the ownership of the object has explicitly been set to remain with C++ by invoking QQmlEngine::setObjectOwnership() with QQmlEngine::CppOwnership specified.
Additionally, the QML engine respects the normal QObject parent ownership semantics of Qt C++ objects, and will not ever take ownership of a QObject instance which already has a parent.
The simplest solution would be to assign a parent to each CGame object before returning it to QML. Alternatively, you can do the following for each object:
QQmlEngine::setObjectOwnership(game, QQmlEngine::CppOwnership);
i think you will create your QList from C++, and put in qml like a contextProperty (with that you dont need declare in qml the class, but you still need register type), but the problem is you need refresh (executing again the sentence of declaration of contextPropertie) for every change in the model, and yes, is better user QObject*.
Maybe is better try use a QmlListProperty (here my case Use QQmlListProperty to show and modify QList in Qml), in youtube exist other tutorial for use that class), or QAbstractListModel (i don't worked before with that class, but tell me is lightly better in some cases).

Passing Q_GADGET as signal parameter from C++ to QML

Can't get a property of a C++ object inside a QML code.
Object is passed as a parameter to the signal.
Expected that in QML, the property text of the Record object can be extracted. And the value should be abc. QML sees the object as QVariant(Record), and its property text as undefined.
Record is a value-type like QPoint, so it uses Q_GADGET declaration.
hpp:
#ifndef LISTENP_HPP_
#define LISTENP_HPP_
#include <QObject>
#include "Record.hpp"
class ListenP: public QObject
{
Q_OBJECT
public:
ListenP();
virtual ~ListenP();
void emitGotRecord();
signals:
void gotRecord(Record r);
};
#endif /* LISTENP_HPP_ */
cpp:
#include "ListenP.hpp"
ListenP::ListenP() :
QObject()
{
}
ListenP::~ListenP()
{
}
void ListenP::emitGotRecord()
{
emit gotRecord(Record("abc"));
}
hpp for Record:
#ifndef RECORD_HPP_
#define RECORD_HPP_
#include <QObject>
#include <QMetaType>
class Record
{
Q_GADGET
Q_PROPERTY(QString text READ text WRITE setText)
public:
Record(const QString& text = "");
~Record();
QString text() const
{
return m_text;
}
void setText(const QString& text)
{
m_text = text;
}
private:
QString m_text;
};
Q_DECLARE_METATYPE(Record)
#endif /* RECORD_HPP_ */
cpp for Record:
#include "Record.hpp"
Record::Record(const QString& text) :
m_text(text)
{
}
Record::~Record()
{
}
namespace
{
const int RecordMetaTypeId = qMetaTypeId<Record>();
}
QML piece:
Connections {
target: listenPModel
onGotRecord: {
console.log(r)
console.log(r.text)
}
}
main piece:
QGuiApplication app(argc, argv);
auto listenP = std::make_shared<ListenP>();
QQuickView view;
view.rootContext()->setContextProperty("listenPModel", &*listenP);
view.setSource(QStringLiteral("src/qml/main.qml"));
view.show();
QtConcurrent::run([=]
{
QThread::sleep(3);
listenP->emitGotRecord();
});
return app.exec();
Log shows:
qml: QVariant(Record)
qml: undefined
The release notes for Qt 5.5 says for the new features:
Qt Core
You can now have Q_PROPERTY and Q_INVOKABLE within a Q_GADGET, and there is a way to query the QMetaObject of such gadget using the QMetaType system
Indeed, compiling and running your example with Qt 5.4 gives the same result as yours whereas with Qt 5.5 I got Record correctly recognised, i.e. I got as a result:
qml: Record(abc)
qml: abc
Also, as stated in the Q_DECLARE_METATYPE documentation, the type passed to the macro - Record in this case, should provide (1) a public default constructor, (2) a public copy constructor and (3) a public destructor. Since Record is a very simple class, there's no need to provide a copy constructor as the default one is sufficient.