PDA

View Full Version : Exposing a Qt container property to QtScript, READ and WRITE



SingleMalt
20th April 2011, 19:52
I am attempting to expose a Q_PROPERTY that references a Qt container to QtScript, that allows for both reading from the container and writing to the container. According to all the Qt documentation, this should be possible. I have spent a week trying everything I can think of, and searching this forum and the whole of the Google index to no avail. There are several similar threads on this forum, but none answer the question, really.

Everything I have tried allows the script to see the values in the container, but not alter the values in that container. Even the QtScript Debugger sees the values, but cannot modify them interactively.

Minimal, compilable code follows:

myclass.h (inlined for ease of posting)

class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList strings READ getStrings WRITE setStrings);

public:
MyClass() { strings << "abc" << "def" << "ghi" << "jkl"; };
~MyClass() {};

QStringList getStrings() const { return strings; };

public slots:
void setStrings(const QStringList& list) { strings = list; };

private:
QStringList strings;
};

main.cpp

#include "myclass.h"

Q_DECLARE_METATYPE(QStringList);

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

QScriptEngine *scriptEngine = new QScriptEngine;
qScriptRegisterSequenceMetaType<QStringList>(scriptEngine);

MyClass *myclass = new MyClass();
QScriptValue value = scriptEngine->newQObject(myclass);
scriptEngine->globalObject().setProperty("AAA", value);

qDebug("Before: " + myclass->getStrings().join("~").toAscii());

scriptEngine->evaluate("AAA.strings[1] = 'xxx';");

qDebug("After : " + myclass->getStrings().join("~").toAscii());
}

Output:

Before: abc~def~ghi~jkl
After : abc~def~ghi~jkl

There is something obvious I am missing, but for the life of me, I can't figure it out. This is not limited to the QStringList container, I cannot WRITE to QVector, QList, QMap, or any other Qt containers.

Another hint: A breakpoint that is put on the "setStrings" method is never triggered, indicating to me that something is wrong with my syntax, in that the script engine has not been instructed properly to bind with my WRITE method.

I have also tried using the "qScriptRegisterMetaType" approach, and defining my own methods for converting the QStringList to and from QScriptValue, but that has not been successful either.

Representative threads (there are many) where this same topic has gone unanswered:
http://www.qtcentre.org/threads/6540-Storing-property-lists-for-custom-designer-plugins
http://www.qtcentre.org/threads/25221-Q_PROPERTY-of-type-QMap

Any pointers are appreciated.

SingleMalt
25th April 2011, 17:49
Well, as 50 people have looked at this since I posted it (at this time of this writing), and there have been no replies, can I assume that this is either:

A bug
Not something that Qt is expected to do?


If anyone could confirm either of those, that would be quite helpful in and of itself.

Here's another thread where wysota (who has > 22,500 posts on this forum), suggests that WRITEing to a containerized Q_PROPERTY is perfectly valid. He even provides code that he claims will work (but doesn't). wysota has commented in the other threads I linked to above as well, so even a veteran poster thinks this should work.

http://www.qtcentre.org/threads/18849-Plotting-points

OTOH, is there something obvious I am missing?

SingleMalt
2nd May 2011, 14:45
Yea! 102 views.

Here are some more examples of code that should work:
http://code.google.com/p/optra/source/browse/trunk/client/optraclient/zones/database/src/databaseinterface.h?r=19
http://code.google.com/p/optra/source/browse/trunk/client/optraclient/zones/database/src/databaseinterface.cpp?r=19

There are a number of examples one can find where projects tried to do something like "Q_PROPERTY(QList<QVariant>..." as in the above links, and then in future revisions, removed that code, because it simply didn't work.

And here's a little bit of KDE that uses a Q_PROPERTY with a container:
http://api.kde.org/4.x-api/kdelibs-apidocs/kdeui/html/kcolorcombo_8h_source.html
http://api.kde.org/4.x-api/kdelibs-apidocs/kdeui/html/kcolorcombo_8cpp_source.html

I assume this code works correctly, but I don't know why. I frankly don't have the ability to run KDE in a debugger and step through it. I have created some sample code modeled from the code in the above links, including the private class design pattern, but have still not had any luck.

Also, I have tried setting the property from C++ code, like so:


QStringList newStrings;
newStrings << "www" << "xxx" << "yyy" << "zzz";
myclass->setProperty("AAA", newStrings);

This doesn't work either, so maybe this problem is not specific to QtScript, but is an overall limitation with Qt's property system? From my research, it sure looks like containers should work with Q_PROPERTY, but maybe I am wrong. Anyone have any idea?

SingleMalt
7th May 2011, 14:57
With the release of 4.7.3 I thought I would update this thread as it still doesn't work.

wysota: are you around? All of your examples in this forum indicate that this should work. Any thoughts?

I'll update my compilable code example too, as I have determined that this is indeed a direct problem with QtScript; this works just fine when using property system methods.

main.cpp

#include <QtCore\QCoreApplication>
#include <QtScript>
#include "myclass.h"

Q_DECLARE_METATYPE(QStringList);

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

qRegisterMetaType<QStringList>();
MyClass *myclass = new MyClass();

qDebug("Before: " + myclass->getStrings().join("~").toAscii());

// Test 1 - set property via setProperty (WORKS AS EXPECTED)
QStringList newStrings;
newStrings << "www" << "xxx" << "yyy" << "zzz";
myclass->setProperty("strings", newStrings);
qDebug("Test1: " + myclass->getStrings().join("~").toAscii());

// Test 2 - set property via QtScript (FAILS SILENTLY)
QScriptEngine scriptEngine;
QScriptValue scriptQObject = scriptEngine.newQObject(myclass);
scriptEngine.globalObject().setProperty("myclass", scriptQObject);
scriptEngine.evaluate("myclass.strings[0] = 'aaa';");
qDebug("Test2: " + myclass->getStrings().join("~").toAscii());
}

myclass.h

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>
#include <QStringList>
#include <QVariant>

class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList strings READ getStrings WRITE setStrings);

public:
MyClass() { strings << "abc" << "def" << "ghi" << "jkl"; };
~MyClass() {};

QStringList getStrings() const { return strings; };
void setStrings(const QStringList& list) { strings = list; };

QStringList strings;
};

#endif // MYCLASS_H

Actual output:

Before: abc~def~ghi~jkl
Test1: www~xxx~yyy~zzz
Test2: www~xxx~yyy~zzz

Expected output:

Before: abc~def~ghi~jkl
Test1: www~xxx~yyy~zzz
Test2: aaa~xxx~yyy~zzz

As before, the setStrings method never gets called when the script is executed. Is the signature of that method wrong? All the examples that I can find in this forum and elsewhere have a signature like that.

SingleMalt
9th May 2011, 04:45
So I'm either an idiot who doesn't know a compiler from their arsehole, or this does not operate as advertised?

Anyone have an opinion? Seriously? Three weeks, 200 views and not a *single* person has an opinion?

SingleMalt
11th May 2011, 18:28
http://bugreports.qt.nokia.com/browse/QTBUG-19204

wysota
12th May 2011, 08:31
Sorry, I haven't noticed your thread earlier. So you say this doesn't work?


#include <QtGui>

class Object : public QObject {
Q_OBJECT
Q_PROPERTY(QStringList list READ list WRITE setList)
public:
const QStringList &list() const { return m_sl; }
public slots:
void setList(const QStringList &sl) { m_sl = sl; }
private:
QStringList m_sl;
};

#include "main.moc"

int main(int argc, char **argv){
// QApplication app(argc, argv);
Object o;
QStringList sl = QStringList() << "a" << "b" << "c";
o.setList(sl);
qDebug() << o.list();
return 0;
}
It works perfectly fine for me...

Edit: By the way, this also works, if added near the end of main():


QScriptEngine engine;
QScriptValue v = engine.newQObject(&o);
engine.globalObject().setProperty("o", v);
qDebug() << "READ:" << engine.evaluate("o.list").toString();
engine.evaluate("var arr = new Array; arr[0] = 'd'; o.list = arr;").toString();
qDebug() << "WRITE:" << engine.evaluate("o.list").toString();

or even this:

qDebug() << "READ:" << engine.evaluate("o.list").toString();
engine.evaluate("o.list = ['q', 'w', 'e', 'r', 't', 'y'];").toString();
qDebug() << "WRITE:" << engine.evaluate("o.list").toString();