nesting multiple Q_INVOKABLE methods



  • Goal: To visually display DataPieces with an appropriate line of output. (ex: "Motor Generator - Voltage: 42V (min=12,max=400))

    Data Structure: DataModel contains multiple DataModules, which then contains multiple DataPieces.

    Approach: I have set a DataModel (loaded with data) object as a context property of my root context. My plan is to call a Q_INVOKABLE method to get each DataModule, and either call a second Q_INVOKABLE to retrieve the appropriate text output (I have it formatted so each DataModule contains a body of text for all its DataPieces), or use its Q_PROPERTY to get its textBody.

    Problem: I don't really know how to structure this inside of QML. Because I have the dataModel context property, I don't think I actually need to declare a model inside of my ListView; that leaves the delegate. I was trying to do something along the lines of this:

    delegate: Text { text: dataModel.getModuleAt(i).getTextBody }
    

    a) Pretty sure I need a Repeater here, but I don't know exactly how to incorporate that as a delegate.
    b) My main confusion is that even though I have a Q_INVOKABLE method and a Q_PROPERTY defined inside of my DataModule class, I still wasn't getting any method options when I typed the second period after 'getModuleAt(i)'. Why is this?
    c) Is there a better way to do this? For example, I was thinking about using a Repeater in tandem with dataModel to write an actual Model.qml, and then from within the delegate, I could just reference the model to retrieve the textBodies.

    datamodel.h:

     class DataModel: public QObject
    {
        friend class QListView;
        Q_OBJECT
        Q_PROPERTY(int size READ getSize CONSTANT)
    
    public:
        explicit DataModel(QObject * parent = nullptr);
        explicit DataModel(const QString file); //this is our main constructor here.
    
        Q_INVOKABLE DataModule *getModuleAt(int i);
        void addModule(DataModule* mod_);
    
        const QVector<DataModule*> getModel();
        Q_INVOKABLE const QString tester();
        int getSize();
        ~DataModel();
    
    
    
    private:
        QVector<DataModule*> modules; //separates Modules into Rectangles. Spacing modifiable.
        int spacing;
        int size;
    
    };
    

    datamodule.h:

    class DataModule: public QObject
    {
    
        Q_OBJECT
        Q_PROPERTY(QString textBody READ getTextBody)
    
    public:
    
        explicit DataModule(QObject * parent = nullptr): QObject(parent), name(*(new QString(""))),textBody(*(new QString(""))) { }
        explicit DataModule(const DataModule&); //copy construct
        explicit DataModule(DataModule&&); //move construct
    
        DataModule& operator=(const DataModule& thing); //copy assignment
        DataModule& operator=(DataModule&&); //move assignment
    
        Q_INVOKABLE QString getTextBody(); //usable in QML
        const QString Name() const;
        QVector<DataPiece*> Data() const;
    
        void setName(QString&& name_);
        void setTextBody(QString&& body); //can use this for testing purposes.
        void setData(QVector<DataPiece*>&& data_);
    
        void initTextBody(); //forms the textBody of the Module with the DataPieces on hand.
    
        void addData(DataPiece *piece); //this will invoke a move assignment
    
        ~DataModule();
    
    private:
        QString& name;
        QString& textBody; //the text that will appear in the DataModel. These will be inserted into Rectangles, which will then be spaced.
        QVector<DataPiece*> data;
    };
    

    Any suggestions are welcomed. Thank you!



  • @devDawg
    Hi there, i have some questions about your implementation:

      1. Do you pretend to show this information in a ListView ?
      1. Why are you implementing so many Q_INVOKABLE methods ? can you show me a part of your code where are you using this methods ?


  • so this is the ListView portion inside my .qml file:

    ListView {
                                verticalLayoutDirection: ListView.TopToBottom
                                anchors.fill: parent
                                id: midColumn
                                x: 770 / 2
                                y: 15
                                orientation: Qt.Vertical
                                spacing: 3
                                header: Component {
                                    Text{
                                        id: headerText
                                        text: "<h1><i>System Output: Summary</i></h1>"
                                        anchors.horizontalCenter: parent.horizontalCenter
    
                                    }
                                }
                                model: dataModel.size
                                delegate: Component {
                                    Text{
                                        text: dataModel.getTextBodyAt(index)
                                    }
                                }
                            }
    

    Look at main.cpp to see that dataModel is registered as a contextProperty:

    qmlRegisterType<DataPiece>("DataPackage",1,0,"DataPiece");    
        qmlRegisterType<DataModule>("DataPackage",1,0,"DataModule");
    
        QQmlApplicationEngine engine;
        QQmlContext * context = engine.rootContext();
    
        DataModel model("C:\\Users\\AMcFarlane\\Qt\\projects\\UiTest\\mock_loader_test");
        QList<int> freq = {2,3,3,4,6,1,5,4,3,7,6,5,8};
        DataSimulator sim(&model,freq);
    
        if (model.getModel().isEmpty()) {
            qDebug() << "model empty" << endl;
        }
        context->setContextProperty("dataModel",&model);
        context->setContextProperty("dataSim",&sim);
    

    At this point, my GUI is initializing properly with the expected text, like so:
    0_1529437996724_Screenshot (10).png

    My current task is to simulate the fluctuation of these data values and update accordingly. I am trying to accomplish this with a DataSimulator class, which assigns a QTimer to each DataPiece, giving it a random value in its range every timeout(). This, however, is not enough to update the GUI. Because I show textBodies in my delegate, changing the value of DataPiece doesn't do anything; the textBody of DataModule must also be changed.

    Here is a portion of datasimulator.cpp:

    DataSimulator::DataSimulator(DataModel *model, QList<int> frequencies)
    {
        int freqSize = frequencies.size();
    
        for (int i = 0; i < model->getModel().size(); i++) {
            for (DataPiece* piece: model->getModuleAt(i)->Data()) {
                connect(mapper,SIGNAL(mapped(int)),model->getModuleAt(i),SLOT(adjustVal(piece)));
                if (freqSize==0){
                    initiateData(piece,1);
                } else {
                    initiateData(piece,frequencies.takeFirst());
                    freqSize--;
                }
            }
        }
    }
    
    /*This method sets up an individual piece of data with its minimum and maximum, according to a specific frequency.
     */
    void DataSimulator::initiateData(DataPiece * data_, int freq)
    {
        //int newVal = min + (int((max-min)*rand()) / (RAND_MAX + 1));
        QTimer* clock = new QTimer(this);
        timers.append(clock);
        data.append(data_);
        //so with this function, when DataSimulator needs to send a new val, DataPiece is given that new val.
    
        mapper = new QSignalMapper(this);
    
        connect(clock,SIGNAL(timeout()),mapper,SLOT(map()));
        mapper->setMapping(clock,timers.size());
    
        clock->setInterval(freq * 1000);
        clock->setTimerType(Qt::TimerType::PreciseTimer);
        clock->start();
        //thread->start();
        //qDebug() << "clock " << timers.indexOf(clock,0) << " started";
    
    }
    

    I tried messing around with QThreads, but when I realized they are kind of complicated, I am now trying to implement my own search-and-replace which, based on the index of the DataPiece within its DataModule, replaces a single index-specified regex with a replacement. If I didn't specify the index, I would risk replacing the values for each DataPiece in the DataModule.

    To answer your second question, I really only need 1 Q_INVOKABLE, getTextBodyAt(i); I just haven't cleaned up my code since I realized that.

    Yes, I know, my method is very unorthodox. I have already gotten this far in the implementation that I am going to try and make the search&replace work, otherwise I will have to make some deeply nested changes.



  • @devDawg
    if you want to dinamically change the textBody when the DataPiece values are changed, you should to use NOTIFY in your Q_PROPERTY.

    Q_PROPERTY(QString textBody READ getTextBody NOTIFY textBodyChanged)
    
    ...
    
    signals:
      void textBodyChanged(const QString &newTextBody);
    
    

    and call the property in your delegate:

     Text{
        text : dataModel.getTextBodyAt(index).textBody
     }
    

    The value in UI is changed when Notify signal is emitted.

    Below is an example using TextField and Text to show how this interation works:

    main.cpp

    #include <QApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "cppobject.h"
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
        QQmlApplicationEngine engine;
    
        CppObject cppObject;
        
        engine.rootContext()->setContextProperty("cppobject", &cppObject);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    

    cppobject.h

    #ifndef CPPOBJECT_H
    #define CPPOBJECT_H
    
    #include <QObject>
    #include <QDebug>
    
    class CppObject : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString name READ getName NOTIFY nameChanged)
    
    public:
        CppObject() {
            m_name = "No Name";
        }
    
        Q_INVOKABLE void setNameWithNotify(const QString &name){
            qDebug() << "Name Changed: " + m_name + " -> " + name + " AND emitting signal";
            m_name = name;
            emit nameChanged(m_name);
        }
    
        Q_INVOKABLE void setNameWithoutNotify(const QString &name){
            qDebug() << "Name Changed: " + m_name + " -> " + name;
            m_name = name;
        }
    
    signals:
        void nameChanged(const QString &newName);
    
    private:
        QString m_name;
        QString getName(){
            return this->m_name;
        }
    };
    
    #endif // CPPOBJECT_H
    

    main.qml

    import QtQuick 2.4
    import QtQuick.Window 2.1
    import QtQuick.Layouts 1.2
    import QtQuick.Controls 2.0
    
    Window {
        id: window
        width: 800
        height: 600
        minimumHeight: 350
        minimumWidth: 500
        visible: true
        color: "#222222"
    
        ColumnLayout{
            width: 450
            height: 150
            anchors.centerIn: parent
    
            RowLayout{
                Item{ Layout.fillWidth: true }
                Text {
                    text: "Name: "
                    color: "white"
                }
                TextField{
                    id: nameTextField
                    text: "No Name"
                }
                Item{ Layout.fillWidth: true }
            }
    
            Item{  Layout.fillHeight: true }
    
            RowLayout{
                Item{ Layout.fillWidth: true }
    
                Text {
                    text: cppobject.name // use Q_PROPERTY
                    color: "white"
                }
                Item{ Layout.fillWidth: true }
            }
    
            Item{ Layout.fillHeight: true }
    
            RowLayout{
                Item{ Layout.fillWidth: true  }
                Button{
                    id: button1
                    text : "Save (With Notify)"
                    onClicked: cppobject.setNameWithNotify(nameTextField.text)
                }
    
                Button{
                    id: button2
                    text: "Save (Without Notify)"
                    onClicked: cppobject.setNameWithoutNotify(nameTextField.text)
                }
                Item{ Layout.fillWidth: true }
            }
        }
    }
    


  • @KillerSmath That is a highly reasonable approach. I will check back when I have had time to implement this.

    Thank you!



  • @KillerSmath Its working! NOTIFY did the trick. The final touch that I also included was to use a Q_INVOKABLE getModuleAt(i) instead of a Q_INVOKABLE getTextBodyAt(i); this allowed me direct access to the DataModule's property:

     Text{   text: dataModel.getModuleAt(index).TextBody }
    

    portion of datamodule.h:

    class DataModule: public QObject
    {
    
        Q_OBJECT
        Q_PROPERTY(QString TextBody MEMBER textBody NOTIFY textBodyChanged)
    .
    .
    .
    signals:
        void newVal(DataPiece*);
        void textBodyChanged(QString newText);
    

    Q_INVOKABLE method inside of the QML context class datamodel.h:

     Q_INVOKABLE DataModule *getModuleAt(int i);
    

    Brilliant! Thanks for the help mate. Hopefully this is helpful to others as well.