everclear
everclear

Reputation: 332

Setting a Qml Context Property in the Qt5.7 QtQuick2 application template

My goal is to populate a combo box with serial ports available on the (Windows) machine. I created a wrapper for QSerialPortInfo that I would like to publish as QML root context property. I used the QtQuick2 application template as starting point.

#include <QList>
#include <QObject>

class QuickSerialPortInformer : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<QString> portselectionmodel READ portselectionmodel CONST)

public:
    static const QuickSerialPortInformer *getInstance();

    QList<QString> portselectionmodel() const;

protected:
    QuickSerialPortInformer();

private:
    static const QuickSerialPortInformer *Instance;
};

#endif // QUICKSERIALPORTINFORMER_H

I register the object like this:

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    /* here */ engine.rootContext()->setContextProperty("serialPortInformer", QuickSerialPortInformer::getInstance());
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();
}

The QList<QString> returned by the property shall serve as model for the combobox. My guess is that QList<QString> is converted to a JavaScript array of strings and then converted back to whatever the C++ part of the ComboBox's model property needs. After all, the property accepts an array of strings defined in JavaScript. However, it seems that when assigning the property of the QuickSerialPortInformer to the model of the ComboBox no such marshalling is done. As pointed out in the comments, only some C++-types may be used as models for QtQuick views. One of them is QStringList, which I am using now.

ComboBox {
    id: portSelector
    model: serialPortInformer.portselectionmodel
}

My problem is that SerialPortInformer is unknown in the qml context. I suspect that the context I am using to register the object is not visible to the script I am executing with engine.load(). I tried to reverse the order of load() and setContextProperty() to no avail. What am I missing?

After renaming SerialPortInformer to serialPortInformer as suggested in the comments and documentation another problem arises. serialPortInformer is a boolean of value true in QML instead of the expected object.

I was able to solve the "boolean"-mystery to the extent that I have been able to make my use case work. The answer is that my SerialPortInformer::getInstance() was returning a pointer to const. As soon as I removed the const-qualifier serialPortInformer was of type object and exposed the desired attribute. The only reason I can think of why this compiled in the first place is that the const QVariant & overload of the method was called instead, using a non-explicit constructor. So my note-to-self is: Never register pointers to constant QObjects in a QML context

Upvotes: 3

Views: 1254

Answers (2)

Vedanshu
Vedanshu

Reputation: 2296

Since you just want to read the QStringList, just make QStringList as the return type of a Q_INVOKABLE method. Also, I don't see you creating an object of QuickSerialPortInformer which you might be creating in your original code. To sum up your code should something like this,

#include <QList>
#include <QObject>

class QuickSerialPortInformer : public QObject
{
    Q_OBJECT
    Q_INVOKABLE QStringList portselectionmodel();
    ....
};

main.cpp

int main(int argc, char *argv[])
{
    ...
    QuickSerialPortInformer portInformer;
    QQmlApplicationEngine engine; 
    engine.rootContext()->setContextProperty("serialPortInformer",&portInformer);
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();
}

Upvotes: 1

Francisco Hernandez
Francisco Hernandez

Reputation: 312

First, implement your Q_PROPERTY as follows (see documentation):

Q_PROPERTY(QStringList portSelectionModel READ portSelectionModel WRITE setPortSelectionModel NOTIFY portSelectionModelChanged)

Not sure why your singleton is not working, but try implementing it like this:

public:
static QuickSerialPortInformer& Instance()
{
    static QuickSerialPortInformer instance; 
    return instance;
}
~QuickSerialPortInformer()=default;

private:
QuickSerialPortInformer()=default;
QuickSerialPortInformer(QuickSerialPortInformer const&)=delete;
void operator = (QuickSerialPortInformer const&)=delete;

On your main.cpp:

QQmlContext* quickSerialPortInformerCtx = engine.rootContext();
quickSerialPortInformerCtx->setContextProperty("quickserialportinformer", &QuickSerialPortInformer::Instance());

On your qml:

ComboBox {
    model: quickserialportinformer.portSelectionModel
}

It should work, I've used this implementation before.

Upvotes: 0

Related Questions