Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. nesting multiple Q_INVOKABLE methods
QtWS25 Last Chance

nesting multiple Q_INVOKABLE methods

Scheduled Pinned Locked Moved Solved QML and Qt Quick
repeaternestedinvokableproperty
6 Posts 2 Posters 1.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • devDawgD Offline
    devDawgD Offline
    devDawg
    wrote on last edited by
    #1

    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!

    Anything worthwhile is never achieved easily.

    1 Reply Last reply
    0
    • KillerSmathK Offline
      KillerSmathK Offline
      KillerSmath
      wrote on last edited by KillerSmath
      #2

      @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 ?

      @Computer Science Student - Brazil
      Web Developer and Researcher
      “Sometimes it’s the people no one imagines anything of who do the things that no one can imagine.” - Alan Turing

      1 Reply Last reply
      0
      • devDawgD Offline
        devDawgD Offline
        devDawg
        wrote on last edited by
        #3

        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.

        Anything worthwhile is never achieved easily.

        1 Reply Last reply
        0
        • KillerSmathK Offline
          KillerSmathK Offline
          KillerSmath
          wrote on last edited by KillerSmath
          #4

          @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 }
                  }
              }
          }
          

          @Computer Science Student - Brazil
          Web Developer and Researcher
          “Sometimes it’s the people no one imagines anything of who do the things that no one can imagine.” - Alan Turing

          devDawgD 1 Reply Last reply
          1
          • KillerSmathK KillerSmath

            @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 }
                    }
                }
            }
            
            devDawgD Offline
            devDawgD Offline
            devDawg
            wrote on last edited by
            #5

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

            Thank you!

            Anything worthwhile is never achieved easily.

            1 Reply Last reply
            0
            • devDawgD Offline
              devDawgD Offline
              devDawg
              wrote on last edited by
              #6

              @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.

              Anything worthwhile is never achieved easily.

              1 Reply Last reply
              2

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved