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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.