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:-
- Do you pretend to show this information in a ListView ?
-
- 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:
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.