10

I made a class derived from QAbstractListModel and re-implemented all the necessary functions. I create an object of it, fill in some initial data into the model (all beginInsertRows etc done) and then pass it (the object) to qml via setContextProperty. I'm using QQuickView. Once qml is shown by show() command, I spawn a QThread in which I use the pointer of the Model object. The thread keeps adding data to the model via direct call to Model's addData(..) function (which has beginInsertRows etc).

Problem: There is no update of the UI. I get the following listed in the Application Output pane in QtCreator:

QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)

The form here shows the exactly same problem but I can't use signal and slot as mentioned there.

EDIT:

Running addData() from main thread (using signal-slot as mentioned in the link but not subclassing QThread) did update the ListView to the initial data but the view is not updated after dataChanged() signal. Let me explain it little bit more clearly.

/*This is the class in which data is stored*/
class ClientCardModel : public QAbstractListModel {
    ....
    QList<ClientCard*> m_list;
};

...
ClientCardModel hand;    // object to send to qml

...     
 // New worker thread created and all the following codes are exexuted in that
 QThread *workerThread = new QThread; 

/*New data is created to be inserted in model*/
ClientCard* ccard = new ClientCard;     //New card with all initial values
hand.addData(ccard);   
//Many more card are created and inserted

...
// Lots of coding takes place
/*
  Other code
*/

...
UpdateFieldCard(ccard);    //Function to update the values of ccard
emit hand.dataChanged(index(0), index(rowCount()-1));
... 
/*workerThread is pauded using QWaitCondition*/

ListView in qml is showing only the initial data, i.e, initial data of ccard when it was inserted using addData(). ListView is not getting updated after emitting dataChanged() signal (actually sometimes during debugging the list get updated but the behaviour is unpredictable). Also QQmlChangeSet error I was getting earlier is gone. Should there be some time difference between beginInsertRows() and dataChanged()? Whether I call dataChanged() from main thread or worker thread, it's not updating. Please give suggestions.

2
  • Add qmlRegisterMetaType<QQmlChangeSet>() somewhere where it's executed before the connect, e.g. in the ctor of your model class. How you describe that addData() from another thread it doesn't sound like that approach will work though, as QAbstractItemModel isn't threadsafe. Commented May 24, 2015 at 8:09
  • Did you mean qRegisterMetaType? Also I'm getting 'QQmlChangeSet' was not declared in this scope. So how do I create model in c++ to pass it in QML ? QAbstractItemModel seemed to solve all my problems.
    – Vedanshu
    Commented May 24, 2015 at 9:03

2 Answers 2

2

Previously I was using signal and slot mechanism to add data by passing reference(as told in the forum in the link given in question). Problem with that was, it was not updating after the data was changed and dataChanged() signal was emitted. On google I found that passing values by reference in signal/slot connections is not a good idea. I shall quote what I found here

The receiving slot would not be able to modify the original anyway (while being mislead into thinking that it could). Further, the point behind Qt signals/slots is that the signal emitter does not know what is connected to it, or how many connections there are. If there are two connected slots, and they both try to modify the object that was passed by reference then you have all sorts of potential for bugs. It will work, in limited situations, with non-queued connections, but it's still bad design.

So then I decided to pass by reference which also turned out to be a bad idea.You can't pass a non-const reference as an argument in signals and slots when using a queued connection.

If it were desired to pass by reference, use of QMutex to prevent issues from multiple listeners modifying the data would protect from problems. This, however is academic because in QMetaObject::activate, if the connection type is AutoConnection and sender and receiver are in different threads, the value gets copied. See here: woboq.com/blog/how-qt-signals-slots-work.html

The solution to my problem is that the data should be updated from the main thread instead from worker thread and to do so I used Queued connection as stated in Qt doc (the methods object should be declared in main thread) and made the method invokable. This worked for me. But still in rare case I get wrong result which I think might be due to some code running the worker thread.

1

The model needs to be reset within the UI thread but not from within the worker thread. To achieve this forward the callback as a signal into the Ui thread. The slot within the UI should than read the data.

Some pseudo C++/code:

class MyClass {

    public:
    MyClass() {
        QObject::connect(this, &MyClass::newDataAvailable,
                         this, &MyClass::onNewDataAvailable);
    }

    void newDataAvailableCallback() { emit newDataAvailable(); }

    signals:
    void newDataAvailable();        // private signals not available
                                    // Use PIMPL pattern to hide it

    private slots:
    void onNewDataAvailable() {
        beginResetModel();
        NewData data = worker.getNewData();
        endResetModel();
    }
};

WorkerThread worker;
worker.register(&MyClass::newDataAvailableCallback);
worker.run();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.