Passing an object from QML to C++ - c++

I am working on a simple app to demonstrate integration of C++ with QML but I have problems. In nutshell I want to create a C++ object in qml on fly and pass it to C++ for processing. Here is my code.
import QtQuick 2.4
import QtQuick.Window 2.2
import test 1.0
Window {
visible: true
MainForm {
anchors.fill: parent
mouseArea.onClicked: {
console.log("clicked")
var student = Qt.createComponent("Student")
student.name = "Hello frome QML";
console.log( student.name ) // good
school.addStudent( student )
}
}
}
Student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
QString m_name;
public:
explicit Student(QObject *parent = 0);
~Student();
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
QString name() const
{
return m_name;
}
signals:
void nameChanged(QString arg);
public slots:
void setName(QString arg)
{
if (m_name == arg)
return;
m_name = arg;
emit nameChanged(arg);
}
};
#endif // STUDENT_H
Now when I addStudent() to the school class, there is where there is trouble. The concern .cpp file below.
#include "school.h"
#include <QDebug>
School::School(QObject *parent) : QObject(parent)
{
}
School::~School()
{
}
// why does this not work? it cause compiler error..can't access private members of QObject
//void School::addStudent(Student student)
//{
//// students.append( &student );
//// qDebug() << "added";
//// qDebug() << student.name();// << " was added to school";
//}
void School::addStudent(Student * student)
{
students.append( student );
qDebug() << "added";
qDebug() << student->name();// if this line is there, it breaks the application
}
The questions are also in comments inside the code but the summaries:
Why does addStudent() crashes when I try to access student.name? I can access it though in the qml file after I set it.
Why does my project not compile if I pass Studentby value like seen in the code?
Additional thoughts
It seems like it may have to do with the C++ object getting destroyed by the time C++ function execute. What is the life cycle of C++ object created in QML? When does it gets destroyed? Could the lifetime of the object be an issue here?

Assume that you register the Student type to QML via qmlRegisterType in cpp file.
In your QML, Qt.createComponent("Student") does not create a Student object but a QQmlComponent. Try to set properties other than name and it still works since the object is not a Student. Instead, you should create an object from a pre-defined component via Component.createObject:
MainForm {
id: mainForm
mouseArea.onClicked: {
var student = studentComponent.createObject(mainForm);
student.name = "Hello frome QML";
school.addStudent( student );
}
Component {
id: studentComponent
Student{}
}
}
And everything works fine now.
Back to your code,
//QML
var student = Qt.createComponent("Student")
student.name = "Hello frome QML";
console.log( student.name ) // good
No, it's not good. student.abcdefg = "Hello from QML" works, too. Add console.log(student) and you can see that student is not a Student.
Why does addStudent() crashes when I try to access student.name? I can access it though in the qml file after I set it.
It crashed since the object passed from QML is not a pointer Student. QML engine passes a null pointer to this function. What you accessed in QML is not a Student.
Why does my project not compile if I pass Student by value like seen in the code?
Because Student is an QObject, which is not copyable. Pass by pointer instead.
What is the life cycle of C++ object created in QML? When does it gets destroyed? Could the lifetime of the object be an issue here?
The lifetime is not a issue in your QML since what you created is a component, not an object. When creating Student object in this answer,
var student = studentComponent.createObject(mainForm);
the newly created object sets mainForm as it's parent. The object won't be deleted until mainForm is released. Except you delete it explicitly.

Try moving the declaration of the function QString name() const in the declaration of class Student to before the Q_PROPERTY macro. Or specify "public:" again before the name function. The macro is resetting the access specifier to "private".

Related

Changing members of Q_GADGET from QML

I am trying to propagate Q_GADGET as a Q_PROPERTY into QML, change it there and pass it back into C++.
I have class that derives from Q_OBJECT, which has the Q_GADGET class as a member.
class Foo : public QObject
{
Q_OBJECT
Q_PROPERTY(QGadgetClass bar READ bar WRITE setBar NOTIFY barChanged)
public:
...
QGadgetClass bar() const { return bar_; }
void setBar(const QGadgetClass &bar) { bar_ = bar; emit barChanged(); }
...
signals:
void barChanged();
private:
QGadgetClass bar_;
}
The Q_GADGET class looks like this:
class QGadgetClass
{
Q_GADGET
Q_PROPERTY(AnotherQGadgetClass test READ test WRITE setTest)
... // there are also properties ID & name
public:
...
// QGadgetClass getMyself() const { return *this; } // function explained later
AnotherQGadgetClass test() const { return test; }
void setTest(const AnotherQGadgetClass &test) { test_ = test; }
...
private:
AnotherQGadgetClass test_;
}
Q_DECLARE_METATYPE(QGadgetClass)
I am trying to access Q_GADGET from QML classic way like accessing a Q_OBJECT, but the setters are not called. If I get AnotherQGadgetClass via getter and change it's properties, the setters are called and everything works, but for some reason I cannot manipulate the QGadgetClass. My code in QML looks like this:
Item {
property var bar: foo.bar
function changeBar()
{
console.log(bar.name) // works
console.log(bar.id) // works
bar.name = "New name" // the WRITE function of Q_PROPERTY(name ...) is not called
console.log(bar.name) // shows old name
console.log(bar.test) // prints out AnotherQGadgetClass correctly
var temp = bar.test // copies AnotherQGadgetClass correctly
console.log(temp.name) // prints AnotherQGadgetClass's name
temp.name = "New temp name" // setter is called
console.log(temp.name) // prints new name
bar.test = temp // constructor is NOT called
console.log(bar.test) // prints out old AnotherQGadgetClass
// following code works and will be explained bellow this code
var aa = bar.getMyself() // calls the "hackish" method
console.log(aa.name) // prints out name of QGadgetClass
aa.name = "New name" // calls the setter
console.log(aa.name) // prints out new name
}
}
I have done some research already, but found nothing but this page. I have also found some very unpretty solution here and it worked, but I find it very hacky.
Note that every Q_GADGET is declared as metatype via Q_DECLARE_METATYPE(...) & is registered before usage via qRegisterMetaType<...>("...").
Is there any prettier solution to access QGadgetClass directly from QML, without need to call getMyself() method? Why are the Q_GADGET class setters not called?
A Q_GADGET is always treated as a value type in QML: it must be passed by copying. So the object that you manipulate in QML is not the same instance that you created in C++, and property changes aren't visible in the original. Many related issues are linked from https://bugreports.qt.io/browse/QTBUG-82443

C++ member variable class Q_PROPERTY not available in QML

I am attempting to make a new QML QQuick object that will contain a sub-object of QQuickPaintedItem.
Below is a shortened portion of my c++ code
// PDFDocument.h //
class PDFDocument : public QQuickItem
{
public:
Q_OBJECT
Q_PROPERTY( PDFPageView* pageView READ getPageView )
PDFDocument( QQuickItem* parent = nullptr );
~PDFDocument();
PDFPageView* getPageView() { return &m_pageView; }
private:
PDFPageView m_pageView;
};
//PDFDocument.cpp//
PDFDocument::PDFDocument( QQuickItem* parent /*= nullptr*/ )
:QQuickItem( parent )
{
}
//PDFPageView.h//
class PDFPageView : public QQuickPaintedItem
{
public:
Q_OBJECT
Q_PROPERTY( int dpi MEMBER m_dpi NOTIFY dpiChanged )
Q_SIGNALS:
void dpiChanged();
public:
PDFPageView( QQuickItem* parent = nullptr );
~PDFPageView();
void paint( QPainter* painter_p );
private:
int m_dpi = 144; //default dpi to 144
};
Next is the actual QML snippet
PDFDocument
{
id: pdfDocument
anchors
{
fill: parent
centerIn: parent
}
pageView.dpi: 200 //Invalid grouped property access
}
The type is registered in the engine as well
qmlRegisterType<PDFDocument>( "Nordco.TechPubs", 1, 0, "PDFDocument" );
qmlRegisterType<PDFPageView>( "Nordco.TechPubs", 1, 0, "PDFPageView" );
For some reason I am getting an Invalid grouped property access error in the QML. I marked it with a comment in the qml code snippet.
I shortened the code because I have quite a bit, but can edit this post if i forgot to show anything. I feel like I am missing something simple here but cannot seem to get a helpful error. Any ideas?
It appears that I needed to include my namespaces when declaring a Q_Property because it is a macro. Now the custom member variable is accessable.
Q_PROPERTY( TechnicalPublications::PDFPageView* pageView READ getPageView )
Of course in shortening the problem, I excluded the namespaces my classes exist in.

How to make C++ return lists of values or other sophisticated types to QML

I am trying to learn how to exchange data between C++ and QML. Consider the code below, which is a slightly modified version of one of the Qt examples.
// example.qml
import QtQuick 2.0
import People 1.0
BirthdayParty {
host: Person {
name: "Bob Jones"
shoeSize: 12
}
guests: [
Person { name: "Leo Hodges" },
Person { name: "Jack Smith" },
Person { name: "Anne Brown" }
]
Component.onCompleted:
{ console.log(invite("William Green").name)}
}
the interface files Person.h is
//Person.h
#ifndef PERSON_H
#define PERSON_H
#include <QObject>
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize)
public:
Person(QObject *parent = 0);
QString name() const;
void setName(const QString &);
int shoeSize() const;
void setShoeSize(int);
private:
QString m_name;
int m_shoeSize;
};
#endif // PERSON_H
The interface file birthday.h is
#ifndef BIRTHDAYPARTY_H
#define BIRTHDAYPARTY_H
#include <QObject>
#include <QQmlListProperty>
#include "person.h"
class BirthdayParty : public QObject
{
Q_OBJECT
Q_PROPERTY(Person *host READ host WRITE setHost)
Q_PROPERTY(QQmlListProperty<Person> guests READ guests)
public:
BirthdayParty(QObject *parent = 0);
Person *host() const;
void setHost(Person *);
QQmlListProperty<Person> guests();
int guestCount() const;
Person *guest(int) const;
// Called from the QML side
Q_INVOKABLE Person* invite(const QString &name);
private:
Person *m_host;
QList<Person *> m_guests;
};
#endif // BIRTHDAYPARTY_H
Both the Person and BirthdayParty C++ classes have been exposed to QML
by being registered with the QML type system via
qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty");
qmlRegisterType<Person>("People", 1,0, "Person");
in main.cpp (not pasted here for the sake of brevity).
Currently, when BirthdayParty component is completed, the code "invites" another person "William Green" to the party. The invite method returns a pointer to this person, whose name can be accessed from the Javascript side and his name be printed via console.log
However, what if I wanted to return a list of people ( say the names of all the current guests to the party) back to QML? It seems to me according to this page
I would have to use QVariantList in some way. Is this correct? That page, unfortunately, just mentions how to call Javascript functions from C++ and not vice-versa so I am not sure how to proceed.
In, short my question is how does one return sophisticated data-types from C++ back to QML/Javascript.
In your case you want to expose a list of QObject, and in your code is already done through QQmlListProperty:
Q_PROPERTY(QQmlListProperty<Person> guests READ guests)
then to obtain the elements you must do it in the style of js using length:
Component.onCompleted: {
invite("William Green")
for(var i=0; i < guests.length; i++){
var guest = guests[i]
console.log(guest.name)
}
}
Output:
qml: Leo Hodges
qml: Jack Smith
qml: Anne Brown
qml: William Green

Send object from c++ to qml. What about free memory?

There is a model inherited from QAbstractListModel, I use it in qml. One of the properties of this model are parameters, they are specific to the element type of this model. That is one element the parameters of this class TemperatureParam, DifrentParamType another, a third still something... How can I pass an object to qml and to be sure that the memory is freed after use? The code below now works as I need to, but it seems to me that it's not safe.
Param class is so trivial:
class QuickTemperatureParam : public QObject
{
Q_OBJECT
Q_PROPERTY(float param1 READ param1 WRITE setParam1)
//...
};
Model class (Here's what I'm asking):
class SectionsModel : public QAbstractListModel
{
//...
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override
{
//...
int type = getType( idx );
if (type == 1)
{
auto p = new QuickTemperatureParam( idx );
p->deleteLater(); // This is all right or no?
return qVariantFromValue(p);
}
else if (type == 2)
//...
}
};
QML something like this:
ListView {
model: sectionsModel
delegate: Rectangle {
color: model.statusColor
ItemDelegate {
text: model.title
highlighted: ListView.isCurrentItem
onPressed:
switch ( model.type )
{
case SectionType.Temperature:
temperatureParam.openItem(model)
break;
case SectionType.Lighting:
lightingParam.open(model)
break;
}
}
}
}
Popup {
id: temperatureParam
function openItem(model)
{
var p = model.param
params.itemAt(0).range.from = params.itemAt(1).range.from = p.min
params.itemAt(0).range.to = params.itemAt(1).range.to = p.max
params.itemAt(0).range.setValues( p.dayMin, p.dayMax )
params.itemAt(1).range.setValues( p.nightMin, p.nightMax )
open()
}
}
According to the documentation:
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.
Generally an application doesn't need to set an object's ownership explicitly. As you can read here, by default, an object that is created by QML has JavaScriptOwnership.
Objects returned from C++ method calls will be set to JavaScriptOwnership also but this applies only to explicit invocations of Q_INVOKABLE methods or slots.
Because the method data is not an explicit C++ method call, you should consider to set the object ownership to QQmlEngine::JavaScriptOwnership calling setObjectOwnership()
So, in general:
Don't use QQmlEngine::CppOwnership if you want QML to destroy the object. The associated data will be deleted when appropriate (i.e. after the garbage collector has discovered that there are no more live references to the value)
A QSharedPointer probably wouldn't work. You have more information here.

Qt invoking qml function with custom QObject type

I want to invoke a qml method from c++ passing as a parameter a custom type.
This is my custom type (*Note that all the code I post is not exactly what i'm using, i've extracted the relevant parts, so if you try to compile it is possible that some changes have to be made)
Custom.h
class Custom : public QObject
{
Q_OBJECT
Q_PROPERTY(QString ssid READ getSSID WRITE setSSID)
public:
Q_INVOKABLE const QString& getSSID() {
return m_ssid;
}
Q_INVOKABLE void setSSID(const QString& ssid) {
m_ssid = ssid;
}
}
private:
QString m_ssid;
};
Q_DECLARE_METATYPE(NetworkInfo)
Custom.cpp
Custom::Custom(): QObject() {
setSSID("ABC");
}
Custom::Custom(const Custom & other): QObject() {
setSSID(other.getSSID());
}
This is where I call the qml method
void MyClass::myMethod(const Custom &custom) {
QMetaObject::invokeMethod(&m_item,
"qmlMethod", Q_ARG(QVariant, QVariant::fromValue(custom)));
}
where QQuickItem& m_item
I have declared Custom as QML and QT meta types
qmlRegisterType<Custom>("com.mycustom", 1, 0, "Custom");
When i try to access the parameter inside the qml
import com.mycustom 1.0
function(custom) {
console.log(custom.ssid)
}
I get
qml: undefined
If i do console.log(custom) I get
qml: QVariant(Custom)
so, i'd say my type is correctly imported into Qt (I have used it without problems instantiating it directly in Qml with
Custom {
....
}
Now, the question :)
Why can't I access the ssid property ?
Since you seem to have registered everything correctly, you might be encounting a bug that sometimes happen with QML where objects seem to be stuck in a QVariant wrapping. Try the following and see if it work.
import com.mycustom 1.0
property QtObject temp
function(custom) {
temp = custom;
console.log(temp.ssid);
}
Sometimes forcing your instance to be a QtObject will remove the annoying "QVariant wrapper" and solve the issue.