Unsolved use of model/view programming
-
How do you make the view aware of the model, though? Is it something straightforward such as an additional argument on the c'tor?
I get that you use setModel(), but where do you get the argument to put into the setModel() call?
-
@mzimmers
It's your model. That's all. Like you wrote:a Devices object that is derived from QAbstractTableModel.
http://doc.qt.io/qt-5/qabstractitemview.html#setModel
You instantiate some
QAbstractItemModel
. That's all there is to it. -
I can tell that this is another one of those times where I'm being extra-dense. Let me try to pose my question like this -- in the tutorial, there's this example:
int main(int argc, char *argv[]) { QApplication a(argc, argv); QTableView tableView; MyModel myModel(0); tableView.setModel( &myModel ); tableView.show(); return a.exec(); }
This is straightforward enough, because tableView is a top-level object. In my case, however, it's a part of ui, a (private) member of my widget object. I can't access it from main().
class Devices : public QAbstractTableModel { private: ModelData devices; ... } class Worker : public QObject { Q_OBJECT private: Devices devices; ... } int main(int argc, char *argv[]) { int rc; QApplication a(argc, argv); Widget widget; QThread* thread = new QThread; Worker* worker = new Worker(); ... }
So...am I doing it wrong? Or, should I just obtain the address to devices from the worker object, and pass it to the widget object at construction? Or, something else?
-
@mzimmers
Now I'm really jumping in here, because I don't know what's going on. At the risk of sticking my neck out....Since @SGaist wrote:
As suggested in your other thread, your Worker class should rather be a member of your custom model. That way there's no need for your GUI to know anything about your worker.
So your
Worker
would be a member of yourDevices
. In yourmain
, you don't directly create aWorker
, you create aDevices
(which creates its wonWorker
), and you create your view-widget. Then you set the view's model to yourDevices
instance. -
Hi Jon -
No worries about jumping in; I'm quite new to many of these concepts and appreciate all feedback.
I'm still confused about the design that SGaist has proposed. In my (admittedly limited) Qt experience, I'm accustomed to the main() routine creating the worker and the widget objects, and using the signal/slot mechanism to connect them.
I understand that SGaist's proposal entails main creating objects from Devices and Widget, and Devices would contain Worker. (I don't yet understand the rationale for this, but this hopefully will become clear as I go along.) What I don't understand, is how this solves the issue of conveying the model from Devices to Widget. Specifically, in Widget::Widget(), I know I should have a line like:
ui->tableView->setModel(???);
My question is WHAT should replace the "???" above? How do I tell my widget what/where the model is?
This seems like a really simple question, so I'm probably not asking it well. Hopefully someone will get what I'm trying to say here.
-
@mzimmers said in use of model/view programming:
ui->tableView->setModel(???);
My question is WHAT should replace the "???" above? How do I tell my widget what/where the model is?
Hello mzimmers ,
Actually your first question almost answers the second one. I mean, you literally tell to your "Item view", or in other words, any widget inheriting from QAbstractItemView, like QTableView, that the model is the one given with setModel().
ui->tableView.setModel( &myModel );
From this point, the table view will access data from the model, by calling QAbstractItemModel::rowCount(), QAbstractItemModel::columnCount() and QAbstractItemModel::data(), which you must override when inheriting from QAbstractTableModel. These methods are called in standard C++ way, not using signal/slot mechanism, because the view needs the value at the instant is asks for it.
You can also "connect" (using setModel(), not in a signal/slot way) multiple views to a single model. So the view(s) and the model must be in the same thread, otherwise, you may have, and you will certainly have, memory access errors.
This doesn't prevent usage of a worker, inside the model, to handle heavy tasks, like populating or sorting your model and sending results using signals, but to provide the data to the view, you must be in the same thread.
The only way I can see to achieve this design, is to make your own table view and you own model, by reimplementing QAbstractItemView and QAbstractItemModel, with your own mechanism supporting a model in a different thread.
-
@Gojir4 said in use of model/view programming:
Actually your first question almost answers the second one. I mean, you literally tell to your "Item view", or in other words, any widget inheriting from QAbstractItemView, like QTableView, that the model is the one given with setModel().
ui->tableView.setModel( &myModel );OK, let me ask the question this way: this would be my Widget c'tor:
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->tableView->setModel(&myModel) ui->setupUi(this); }
This of course won't compile, because myModel is undefined within the Widget class. Where do I get it from?
-
Make it a member of your class.
Or add a method to Widget that takes a model in parameter and sets it on the view.
-
OK, now we're getting somewhere. Can I simply pass in a pointer to it in the c'tor, then?
-
That's another possibility yes.
-
@mzimmers
Yes, if you'd like to do it there, as per @SGaist 's example.Is your confusion that you don't know that you can, for example, make your example
Widget
constructor take extra parameters beyond what the baseQWidget()
takes? You can pass whatever additional stuff you like to your constructor if you wish to write it like that. -
Thanks, guys. No, my confusion wasn't using the c'tor, it was whether manually passing a pointer to the model, into the display widget, was the right way to do this. I'm going to go implement this, and will return with my next confusion in a bit. Thanks again...
UPDATE:
Believe it or not, I have it working. (Still haven't implemented SGaist's design suggestion but I will.) So, I think the next thing to do is to implement my override of insertRows()/beginInsertRows().
So, is the correct sequence in insertRows() to:
- call beginInsertRows() and add a row
- add the data to be inserted to my private copy of the data
- call endInsertRows()
In other words, will the insertRows() function I write will modify my copy of the data (the model)?
Also, beginInsertRows() has an argument const QModelIndex &parent. What is this, and where do I get it from?
Thanks...
-
I've used the address book example as a guide, and it's sort of working. I can successfully add a row, and populate its contents. But when I try to update a row (actually just one column in the row), the update doesn't show in the widget.
Here's my update code...am I forgetting a window refresh or something? I don't see anything like that in the address book example.
Thanks...
// update the row. Easiest to just do all fields. deviceTable[row.Srow] = device; QModelIndex id = index(row.Srow, 0, QModelIndex()); setData(id, device.macAddr, Qt::EditRole); id = index(row.Srow, 1, QModelIndex()); setData(id, device.devName, Qt::EditRole); id = index(row.Srow, 2, QModelIndex()); setData(id, device.latestHB, Qt::EditRole); //emit a signal to make the view re-read identified data. QModelIndex topLeft = createIndex(0, 0); row.Urow = deviceTable.size(); QModelIndex bottomRight = createIndex(row.Srow, NBR_COLS_IN_TABLE); emit dataChanged(topLeft, bottomRight);
Edit: it was pointed out that my row and column values were off by 1 each; I've corrected that (not reflected in the code above), and the behavior is unchanged.
The field should update about once a second, but it updates whenever it loses or gains focus. Any suggestions are appreciated.
-
If I recall correctly,
dataChanged
should be enough to trigger the view to update. @VRonin can you work your magic here, what are we missing? -
createIndex();
should be called only fromindex()
- You never mentioned where you are potting the code above. In what method is it?
But most importantly:
model and view can not live on 2 different threads. The view will call methods from the model directly and that's a race condition -
- noted (and changed) about createIndex().
- the code in I posted is part of an update routine. When this program receives a message from the target device, it updates the UI to reflect the information in the message. So, this code is in a method in my subclass of QAbstractTableModel.
- I wasn't aware that model/view had to be in the same thread. This will require some attention on my part.
Thanks...
-
@mzimmers said in use of model/view programming:
the code in I posted is part of an update routine. When this program receives a message from the target device, it updates the UI to reflect the information in the message. So, this code is in a method in my subclass of QAbstractTableModel.
If that's how you update your model, why are going through the hell that is subclassing a model rather than just using
QStandardItemModel
? -
@VRonin: I'm new to model/view concepts, and needed an example. I used the address book example, which may not have been the best, but it was all I could find. And it uses the technique I tried to copy. I agree that it's been more trouble than I expected.
-
If you really want to gown that route I recommend chapter 3 of Advanced Qt Programming. There are traps everywhere when you subclass
QAbstractItemModel
-
What I really want is to keep this as simple as possible. Somehow, I get the impression that I've been less than fully successful so far in this endeavor.
The requirements of this project are modest enough: My UI must maintain a small table with information on an handful of devices that are wirelessly connected. Originally it seemed logical to contain the data model within the worker object, but SGaist and other Qt mavens have informed me this would be Doing It Wrong, so I'm looking at changing that. SGaist suggested my worker be part of my model class, but I don't yet understand the benefit of that. Would it be wrong to make the worker, widget and model classes all peers (and created in main())?
At this point, I'm not even sure I need threads, as my socket communications can be made entirely non-blocking.