I'm still struggling with model/view
-
Subclassing models is a minefield. That's why I suggested using
QStandardItemModelinstead. The idea was that you can storestruct DeviceDetails { QString macAddr; QString devName; QString latestTimestamp; };as:
enum DeviceRoles{ macAddr = Qt::UserRole , devName , latestTimestamp }; auto item = new QStandardItem; item->setData(/*...*/,DeviceRoles::macAddr); item->setData(/*...*/,DeviceRoles::devName); item->setData(/*...*/,DeviceRoles::latestTimestamp); myModel.appendRow(item);The model subclass is probably the most correct way but before embarking into it, please read the chapter of Advanced Qt programming I linked in the previous thread and run your custom model through the Model Test
P.S.
AllocatingQStandardItemon the stack is problematic.QStandardItemModelwill take ownership of the item and try to delete it. That means that if you declare the item before the modelQStandardItem myItem; QStandardItemModel myModel;You'll get a crash as well as if you try to remove that row you'll get a crash
-
Subclassing models is a minefield. That's why I suggested using
QStandardItemModelinstead. The idea was that you can storestruct DeviceDetails { QString macAddr; QString devName; QString latestTimestamp; };as:
enum DeviceRoles{ macAddr = Qt::UserRole , devName , latestTimestamp }; auto item = new QStandardItem; item->setData(/*...*/,DeviceRoles::macAddr); item->setData(/*...*/,DeviceRoles::devName); item->setData(/*...*/,DeviceRoles::latestTimestamp); myModel.appendRow(item);The model subclass is probably the most correct way but before embarking into it, please read the chapter of Advanced Qt programming I linked in the previous thread and run your custom model through the Model Test
P.S.
AllocatingQStandardItemon the stack is problematic.QStandardItemModelwill take ownership of the item and try to delete it. That means that if you declare the item before the modelQStandardItem myItem; QStandardItemModel myModel;You'll get a crash as well as if you try to remove that row you'll get a crash
-
@VRonin OK, that makes a lot more sense. So, is the missing field in your setData() examples a pointer to my structure element? Is that how the model keeps track of changes to that data?
-
@mzimmers said in I'm still struggling with model/view:
am I then responsible for manually updating the item when data changes?
Correct, but that would always be the case. if you subclass
QAbstractItemModelit's your responsibility to signal changes of data emitting thedataChangedsignal -
So, if I understand this, the general program flow would be:
- the worker thread receives updates from the various devices
- the worker passes the updates (via a signal) to the model
- the model then either adds rows or modifies existing rows
- the UI automatically reflects the change to the model.
That about right?
One thing I'm still not clear on, is how to modify data in existing rows, for example, changing the timestamp on a device that's already been added.
-
So, if I understand this, the general program flow would be:
- the worker thread receives updates from the various devices
- the worker passes the updates (via a signal) to the model
- the model then either adds rows or modifies existing rows
- the UI automatically reflects the change to the model.
That about right?
One thing I'm still not clear on, is how to modify data in existing rows, for example, changing the timestamp on a device that's already been added.
-
I'm now trying to use QStandardItemModel
Then:
- the worker thread receives updates from the various devices
- the worker passes the updates (via a signal) to a QObject in the main thread that owns the model
- the object inserts/removes/updates the model
- the UI automatically reflects the change to the model.
-
Yes, that's how I'm doing it. (My model class is DeviceModel.) Just as a test, I put this into the c'tor:
DeviceModel::DeviceModel() { QString s1 = "aaa", s2 = "bbb", s3 = "ccc"; QVariant qv; qv = s1; item.setData(qv, DeviceRoles::macAddr); qv = s2; item.setData(qv, DeviceRoles::devName); qv = s3; item.setData(qv, DeviceRoles::latestTimestamp); appendRow(&item); } I'd expect to see this in my QTableView, but I don't. What might I be missing? -
Yes, that's how I'm doing it. (My model class is DeviceModel.) Just as a test, I put this into the c'tor:
DeviceModel::DeviceModel() { QString s1 = "aaa", s2 = "bbb", s3 = "ccc"; QVariant qv; qv = s1; item.setData(qv, DeviceRoles::macAddr); qv = s2; item.setData(qv, DeviceRoles::devName); qv = s3; item.setData(qv, DeviceRoles::latestTimestamp); appendRow(&item); } I'd expect to see this in my QTableView, but I don't. What might I be missing?I'm now trying to use QStandardItemModel
My model class is DeviceModel
Your model class should be
QStandardItemModel. Are you subclassing the model?P.S.
item.as mentioned previously, you should avoid the allocation of
QStandardItemson the stack as the model is supposed to own them -
so it would be like
DeviceModel::DeviceModel() { QString s1 = "aaa", s2 = "bbb", s3 = "ccc"; QVariant qv; QStandardItem * item = new QStandardItem (); // model will clean up qv = s1; item->setData(qv, DeviceRoles::macAddr); qv = s2; item->setData(qv, DeviceRoles::devName); qv = s3; item->setData(qv, DeviceRoles::latestTimestamp); appendRow(item); } -
You should have something like this:
#include <QApplication> #include <QStandardItemModel> #include <QTableView> #include <QDateTime> #include <QTimer> #include <functional> class ModelManager : public QObject{ Q_OBJECT Q_DISABLE_COPY(ModelManager) public: enum DeviceCols{ dc_macAddr ,dc_devName ,dc_latestTimestamp ,dc_count }; explicit ModelManager(QObject* parent = Q_NULLPTR) :QObject(parent) , m_model(new QStandardItemModel(this)) { m_model->insertColumns(0,dc_count); m_model->setHeaderData(dc_macAddr,Qt::Horizontal,tr("Mac Address")); m_model->setHeaderData(dc_devName,Qt::Horizontal,tr("Name")); m_model->setHeaderData(dc_latestTimestamp,Qt::Horizontal,tr("Time Stamp")); } QAbstractItemModel* model() const {return m_model;} public slots: void addDevice(const QString& macAdr, const QString& name, const QDateTime& timeStamp){ const int newRow = m_model->rowCount(); m_model->insertRow(newRow); m_model->setData(m_model->index(newRow,dc_macAddr),macAdr); m_model->setData(m_model->index(newRow,dc_devName),name); m_model->setData(m_model->index(newRow,dc_latestTimestamp),timeStamp); } void updateDevice(int row, const QString& macAdr, const QString& name, const QDateTime& timeStamp){ if(row<0 || row>=m_model->rowCount()) return; m_model->setData(m_model->index(row,dc_macAddr),macAdr); m_model->setData(m_model->index(row,dc_devName),name); m_model->setData(m_model->index(row,dc_latestTimestamp),timeStamp); } private: QAbstractItemModel* m_model; }; int main(int argc, char *argv[]) { QApplication application(argc, argv); ModelManager manager; manager.addDevice(QStringLiteral("Foo"),QStringLiteral("Bar"),QDateTime::currentDateTime()); manager.addDevice(QStringLiteral("Foo1"),QStringLiteral("Bar1"),QDateTime::currentDateTime()); manager.addDevice(QStringLiteral("Foo2"),QStringLiteral("Bar2"),QDateTime::currentDateTime()); QTableView view; view.setModel(manager.model()); view.show(); // simulate adding a device and updating one QTimer::singleShot(1000,&manager,std::bind(&ModelManager::addDevice,&manager,QStringLiteral("AddMac"),QStringLiteral("AddName"),QDateTime::currentDateTime().addSecs(1))); QTimer::singleShot(2000,&manager,std::bind(&ModelManager::updateDevice,&manager,1,QStringLiteral("UpdateMac"),QStringLiteral("UpdateName"),QDateTime::currentDateTime().addSecs(2))); return application.exec(); }Of course, instead of the
QTimerthe updates should come from your workers -
Beautiful.
I made a minor change to the update:
void Device::update(DeviceDetails d) { int rowCnt = m_model->rowCount(); int row ; for(row = 0; row < rowCnt; ++row) { if(m_model->index(row, dc_macAddr).data().toString() == d.macAddr) { break; } } if (row == rowCnt) // didn't find the Mac address; add a new row. { add(d); } else { m_model->setData(m_model->index(row, dc_macAddr), d.macAddr); m_model->setData(m_model->index(row, dc_devName), d.devName); m_model->setData(m_model->index(row, dc_latestTimestamp), d.latestHB); } }Using the QStandardItemModel is indeed much, much simpler than subclassing. Thanks for all the help.