13

My app uses both c++ and QML.

I've defined several objects in C++ part to access SQL etc.

It looks like:

class MyObject : public QObject
{
    Q_OBJECT
public:
    MyObject(QObject *parent = 0);
    Q_INVOKABLE void someFunction(const QString &query);
};

qmlRegisterType<MyObject>("xxx.xxx", 1, 0, "MyObject");

Ideally, I need to use these objects only in Javascript not in QML.

I tried a lot of examples and read all the documentation but still can't solve my problem.

So my questions:

  • How can I instance in Javascript an object defined in C++? I tried var obj = Qt.createComponent("MyObject"); but it seems not works. Is it possible to define new object in normal JS style - var obj = new MyObject;?
  • How can I access this created object in javascript? I tried obj.someFunction("xxx") but got some error - TypeError: Property 'someFunction' of object QQmlComponent(0x3605f5c0) is not a function. What I do wrong here? My object derived from QObject, not from QQmlComponent.
Paul Masri-Stone
  • 2,843
  • 3
  • 29
  • 51
folibis
  • 12,048
  • 6
  • 54
  • 97
  • Someone answered the question and it popped out in my list. :) Using a [context property](http://doc.qt.io/qt-5/qtqml-cppintegration-contextproperties.html) is not a good solution here? The only limitation I can see is the fact that it cannot be instanced on a whim but before the main QML file is loaded. – BaCaRoZzo Aug 06 '15 at 19:37

4 Answers4

4

Your object isn't a Component, but you can use Qt.createQmlObject instead.

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
OliJG
  • 2,650
  • 1
  • 16
  • 15
  • Who should the parent be? Or should it just be deleted dynamically at the end of its usefulness? – Jon McClung Jun 02 '17 at 13:46
  • Unparented objects in QML are managed by the garbage collector unless specifically marked otherwise. Note that there is a difference between the QObject parent and the (visual) parentItem. – OliJG Jun 08 '17 at 14:06
3

The documentation is pretty clear but the confusion looks like it is on the QML side of the equation. This should get you started:

//C++
class MyObject : public QObject
{
    Q_OBJECT
public:
    MyObject(QObject *parent = 0);
    Q_INVOKABLE void someFunction(const QString &query) { qDebug() << query;}
};
....
qmlRegisterType<MyObject>("foo.bar", 1, 0, "MyObject");

The QML is below:

import foo.bar 1.0 //This is your register type
Item {
  MyObject { //here's the instance, remember it is declarative
    id: myObject;
  }
  MyObject {
    id: myObjectInstance2
  }
  Button {
    onClicked: {
      myObject.someFunction("doSomething"); //here is using a reference
      myObjectInstance2.someFunction("doSomethingElse");
    }
  }
}

On clicking you should see the strings in the output (I didn't compile or test this). Be sure to register the type in your main class.

You should check out the local storage object if you're using SQL on a mobile device. It is a pretty simple callback API that works with SQLite. I use it for desktop applications and don't have much trouble. Returning lists is a little annoying so just try to stick to simple types for easy JavaScript integration.

I hope that helps. I absolutely love working in QML, it is quite fun once you learn it (1-2 weeks to be proficient enough to work).

Daniel B. Chapman
  • 4,647
  • 32
  • 42
  • Thanks @Daniel B. Chapman but this is not exactly what I need. I am quite familiar with QML syntax so I asked the question how to do than in JS. My object it is kind of ActiveRecord. Actually it works with Postgresql. The object hasn't visual representation so i see no sence to embed it to QML tree. It can be destroyed or created several times or can be changed ad some step. I need it just like Qt.someFunction() or Math.someFunction(). – folibis May 29 '14 at 05:47
  • Your problem here is you need to instantiate your class as a Component instance. Using this method you need to append (to my knowledge) to the tree so it can be referenced. Essentially `var comp = Qt.create component('myQmlFile.qml'); var instance = comp.createObject(parent);` which is just dynamically declaring this. I should note that a static method is possible (it was a feature request last I checked) but then solution I found was to just to create an instance (dynamic or not) and then use the methods from there. – Daniel B. Chapman May 29 '14 at 13:00
  • Eh, it's very very bad! As I understand it is impossible to expose c++ object to QML as JS library. It was bad idea to use so raw technology. I based my new project on that and now I totally confused what to to with all the code. ( – folibis May 30 '14 at 00:30
  • 1
    It isn't all that and you can expose it. You need to understand the JavaScript side needs a starting point. I use C++ all the time from QML – Daniel B. Chapman May 30 '14 at 12:23
  • I have a very simillar question, could you please take a look: http://stackoverflow.com/questions/39379345/module-is-not-installed-registering-custom-c-wrapper-for-qml – Aras Sep 07 '16 at 21:38
2

You can use

QQmlApplicationEngine engine;
engine.globalObject().setProperty("CppCreator", engine.newQObject(&CppCreator::GetInstance()));

CppCreator is an QObject to create other c++ object

Q_INVOKABLE QObject* Create(const QString& type_name);

Then you can create c++ object in qml js like

var test = CppCreator.Create("Your Type");

It is not perfect but satisfied my requirement. Hope it helps you.

zzy
  • 1,771
  • 1
  • 13
  • 48
2

Here is an example with an imagined TextFile class. First of all, we need a factory class as already suggested:

// Factory class
class Creator : public QObject
{
    Q_OBJECT
    public:
    Q_INVOKABLE QObject* createObject(const QString& typeName, const QVariantMap& arguments);
};

QObject* Creator::createObject(const QString& typeName, const QVariantMap& arguments)
{
    if (typeName == "TextFile")
    {
        QString filePath = arguments.value("filePath").toString();
        TextFile::OpenMode openMode =
                qvariant_cast<TextFile::OpenMode>(arguments.value("openMode", TextFile::ReadWrite));
        QString codec = arguments.value("codec", "UTF-8").toString();
        return new TextFile(qmlEngine(this), filePath, openMode, codec);
    }

    Q_ASSERT(false);
    return nullptr;
}

Note: This class is a bit more complicated than necessary. It is supposed to create multiple types. Now that we have the factory class in place, we need to tell the QML/QJSEngine what to do when calling the operator new for TextFile.

    QJSValue creator = engine.newQObject(new Creator());
    engine.globalObject().setProperty("_creator", creator);
    engine.evaluate("function TextFile(path, mode) { return _creator.createObject(\"TextFile\", { filePath: path, openMode: mode }); }");

Now we can instanciate our TextFile as desired, even with parameters:

var myFile = new TextFile("/path/to/file", TextFile.ReadWrite);

Credits go to the author of this answer.

Richard W
  • 631
  • 4
  • 15