UI not responding to NOTIFY
-
Hi all,
below is a picture of my UI. Each line represents one bit of Data, each running off its own proprietary QTimer (ignore those two lines near the top below Transmission, I have since figured those out). I have each timeout() signal connected to a randomReady() slot inside of the Module that contains the Data. By getting the parent of the signal sender, we are then able to emit the signal valueChanged(DataPiece*),
which is connected to its associated slot, onValueChanged(DataPiece*).In the UI, each Module displays its data information in the displayed textBody. onValueChanged(DataPiece*) first generates a pseudorandom value within the bounds of the DataPiece's min & max values, finds the index of the DataPiece's name in the textBody, then matches a regular expression to replace the value with a new, random value. At the end of this, I emit a signal with the new QString in textBodyChanged, which correlates with NOTIFY within the textBody property of the DataModule.
Problem is, the UI doesn't update. Although my values aren't all that random at times, I am still getting non-zero values that I should be seeing on the screen. Is there some kind of update() function that needs to be called? I will include some of my code below for reference as well.
DataModel constructor that configures my QML context from a .txt file:
DataModel::DataModel(const QString fileName) { int freq = 0; QFile file(fileName); if (!file.open(QIODevice::OpenModeFlag::ReadOnly)){ qDebug() << "file will not open: "<<file.errorString()<<endl; } QTextStream stream(&file); QString&& line = stream.readLine(); int i = 0; std::minstd_rand gen(1); while (!stream.atEnd()) { i++; // qDebug() << i << endl; QChar first = line.front(); if (first == '/') { //next line; we ignore this one. line = stream.readLine(); } else if (first == '#') { //marks the start of a module line.remove(0,1); DataModule* mod = new DataModule(this); mod->setName(std::move(line)); // qDebug() << "name: " << mod->Name() << endl; //here we will load the data pieces into the module. Thus, we go until the first char is '' or '#'. line = stream.readLine(); while (line.at(0)!='#'&&line.at(0)!='%') { //qDebug() << "line: " << line << endl; if (line.at(0)!='-'){ qDebug() << "Error reading data piece. Check the file, and check your method"; break; } QStringList parts = line.split(",",QString::SplitBehavior::KeepEmptyParts,Qt::CaseSensitivity::CaseSensitive); DataPiece* piece = new DataPiece(mod); //parent object will be DataModule parts.first().remove(0,1); //getting rid of the first '-' QStringList prior = parts.at(0).split("-",QString::SplitBehavior::KeepEmptyParts,Qt::CaseSensitivity::CaseSensitive); //prior.at(0) will have the priority, and prior.at(1) will have the name. if (prior.at(0).endsWith("1",Qt::CaseSensitivity::CaseInsensitive) || prior.at(0).endsWith("2",Qt::CaseSensitivity::CaseInsensitive)){ // qDebug() << "loading a Data Piece" << endl; piece->setName(prior.value(1)); //qDebug() << piece->Name(); //name piece->setValue(parts.value(1)); //value // qDebug() << piece->Value(); if (parts.value(2).startsWith("Â",Qt::CaseSensitive)) { QChar ch(0xBA); QString str("C"); str.prepend(ch); piece->setUnits(str); } else piece->setUnits(parts.value(2)); //qDebug() << piece->Units(); piece->setMinimum(parts.value(3)); //min // qDebug() << piece->Minimum(); piece->setMaximum(parts.value(4)); //max // qDebug() << piece->Maximum(); piece->setPriority(prior.value(0)); //prior // qDebug() << piece->Priority(); piece->setWarnMin(parts.value(5)); //warnMin // qDebug() << piece->warnMin(); piece->setWarnMax(parts.value(6)); //warnMax // qDebug() << piece->warnMax(); piece->setCritMin(parts.value(7)); //critMin // qDebug() << piece->critMin(); piece->setCritMax(parts.value(8)); //critMax //qDebug() << piece->critMax(); } mod->addData(piece); connect(piece->Clock(),SIGNAL(timeout()),mod,SLOT(randomReady())); connect(mod,SIGNAL(newVal(DataPiece*)),mod,SLOT(onNewVal(DataPiece*))); // qDebug() << "data piece loaded" << endl; if (stream.atEnd()) qDebug() << "Stream done."; else line = stream.readLine(); } mod->initTextBody(); //qDebug() << mod->getTextBody(); for (DataPiece *thing: mod->Data()) { freq = (gen() % (1000) + 1); thing->startTimer(freq,Qt::TimerType::PreciseTimer); } this->addModule(mod); } else { qDebug() << "Error: Parse out of order ~ "+line; } } qDebug() << "stream done" << endl; size = modules.size(); }
randomReady() inside of DataModule:
void DataModule::randomReady() { DataPiece *holder = qobject_cast<DataPiece*>(this->sender()->parent()); emit newVal(holder); }
onNewVal(DataPiece*) inside of DataModule:
void DataModule::onNewVal(DataPiece *piece) { QString random = QString::fromStdString(std::to_string(piece->Minimum().toInt() + (std::rand() % (piece->Maximum().toInt() - piece->Minimum().toInt() + 1)))); //qDebug() << "random: " << random; piece->setValue(random); int nameLastIndex = 0; if (textBody.contains(piece->Name())) { nameLastIndex = textBody.indexOf(piece->Name(),0,Qt::CaseSensitivity::CaseSensitive) + piece->Name().length() - 1; } else qDebug() << "error: no matching DataPiece name in the text."; QRegularExpression ex("\\-\\s[0-9]+|\\-\\s\\-[0-9]+"); QRegularExpressionMatchIterator it = ex.globalMatch(textBody,nameLastIndex,QRegularExpression::MatchType::NormalMatch); /*Knowing where we started in our textBody, our first match SHOULD be the one we're looking for. */ while (ex.match(textBody).hasMatch()) { if (!it.hasNext()) { it = ex.globalMatch(textBody,0,QRegularExpression::MatchType::NormalMatch); } if (!it.hasNext()) { qDebug() << "something is wrong with these matches: " <<ex.match(textBody).capturedTexts(); break; } QRegularExpressionMatch match = it.next(); //if it actually gets a match, we grab the first one. // qDebug() << "captured at 0: " << match.captured(0); int startInd = match.capturedStart(0); //ind may be zero, but if we put zero here, we would refer to the entire subject string. if (startInd != -1 && startInd == nameLastIndex + 3) { textBody.replace(startInd + 2,match.captured(0).length() - 2,random); // qDebug() << "new val: " << piece->Value(); emit textBodyChanged(textBody); break; } else { qDebug() << "error.. Checking validity: "<< match.isValid(); // qDebug() << match.captured(0); // qDebug() << "text: " << ex.match(textBody).capturedTexts(); // qDebug() << "textBod: " << textBody; } } }
For good measure, here is datamodule.h:
#ifndef DATAMODULE_H #define DATAMODULE_H #include <QVector> #include "datapiece.h" /*A module, in this case, consists of a QVector of DataPieces. */ class DataModule: public QObject { Q_OBJECT Q_PROPERTY(QString TextBody MEMBER textBody NOTIFY textBodyChanged) 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 QString getTextBody(); //usable in QML const QString Name() const; QVector<DataPiece*> Data() const; void setName(QString&& name_); 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(); signals: void newVal(DataPiece*); void textBodyChanged(QString newText); public slots: void onNewVal(DataPiece*); //this slot automatically calls "textBodyChanged(QString)" void randomReady(); 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; }; #endif // DATAMODULE_H
Please let me know if you see something that could cause my problem! Difficult one to Google search, because the problem is so peculiarly strange.
-
Problem solved. I changed what I was getting with my Q_INVOKABLE method inside of the QML document.
Instead of doing this for my ListView delegate:
ListView { . . . model: dataModel.size delegate: Component { Text{ text: dataModel.getTextBodyAt(index) } } }
I'm now doing this:
ListView { . . . model: dataModel.size delegate: Component { Text{ text: dataModel.getModuleAt(index).TextBody } } }
So in essence, instead of getting DataModule's property using a getTextBodyAt(i) method from DataModel, I used getDataModuleAt(i), which gave me direct access to that member's property.
Hopefully this helps others as much as it helped me.