Display several separate table views as a tree
-
OK, here is an example how to build a tree model using QStandardItemModel out of any arbitrary data, in that case I am using QStandardItemModel replicas of the data you provided, you can just as easy do this from SQL query data.
!http://i47.tinypic.com/34fkv1v.png(tree)!
@ // create source data
QStandardItemModel *m1 = new QStandardItemModel(3, 1, this);
QStandardItemModel *m2 = new QStandardItemModel(2, 1, this);
QStandardItemModel *m3 = new QStandardItemModel(2, 1, this);m1->setItem(0, new QStandardItem("Apple")); m1->setItem(1, new QStandardItem("Orange")); m1->setItem(2, new QStandardItem("Banana")); m2->setItem(0, new QStandardItem("Potato")); m2->setItem(1, new QStandardItem("Onion")); m3->setItem(0, new QStandardItem("Beef")); m3->setItem(1, new QStandardItem("Chicken")); QList<QStandardItemModel*> modelList; QList<QString> names; modelList << m1 << m2 << m3; names << "Fruits" << "Vegetables" << "Meats"; // create the "tree model" model = new QStandardItemModel(3, 1, this); // for number of sources for (int i = 0; i < 3; ++i) { QStandardItem *item = new QStandardItem(names.at(i)); // for numbers of records in a source for (int c = 0; c < modelList.at(i)->rowCount(); ++c) { QStandardItem *temp = modelList.at(i)->item(c)->clone(); item->appendRow(temp); } model->setItem(i, item); }@
The model MUST store the data it displays, there is no amount of integration to allow models to fetch dynamically data from SQL, you yourself have to implement the interaction between the model and the database.
You must be thinking of QSqlTableModel, which does all that stuff automatically for you, and thus cannot be used to represent a tree structure, as just like SQL queries, it can only have lists of results, not trees of results.
-
That solution seems more straight forward, because you won't need to implement your own model classes. But, if I understand correctly, it will actually create a separate model for the Tree view (from the data in the Table models). This means you have additional work to do in order to keep everything in "sync" as soon as something in one of the models changes...
-
[quote author="utcenter" date="1358699949"]The model MUST store the data it displays, there is no amount of integration to allow models to fetch dynamically data from SQL, you yourself have to implement the interaction between the model and the database.[/quote]
Then this is not the approach I will take.[quote author="utcenter" date="1358699949"]
You must be thinking of QSqlTableModel[/quote]
This is exactly what I'm thinking of. I want to wrap some sort of tree model around QSqlTableModels.[quote author="MuldeR" date="1358700707"]That solution seems more straight forward, because you won't need to implement your own model classes. But, if I understand correctly, it will actually create a separate model for the Tree view (from the data in the Table models). This means you have additional work to do in order to keep everything in "sync" as soon as something in one of the models changes...[/quote]
I agree, and from experience, this gets messy.[quote author="utcenter" date="1358700888"]Basically, all he needs to do is when a value in the tree view is changed, to run an SQL update with the model index values and the new value to update the value in the database.[/quote]
I want to be able to edit, insert, delete. I want to have multiple editors open at once, reflecting changes to any of the tables. I'm not saying it cannot be done your way, but I thought one of the advantages of the model/view framework was that you could have multiple views upon the same underlying models. That way you don't have to keep parallel representations of the data in sync. -
http://qt-project.org/doc/qt-4.8/model-view-programming.html#note-content-13
[quote]When first starting to work with designing custom models based on QAbstractItemModel, QAbstractTableModel or QAbstractListModel, it is very tempting put your actual data storage inside the newly created model. While that technically works, it is generally not good practice to do so[/quote]
-
@jmelbye - you seem to have the classical case of "unrealistic expectations" syndrome which is so often to see here - many people expect that Qt will be able to do everything they come up with out of the box, which is simply not the case.
While it is true that Qt is a huge library, it mostly provides small building blocks that you yourself must assemble into more functional blogs to build your applications out of. Qt does save a tremendous amount of time, but there are still many things you must do yourself.
To me it sounds like you need to build your own component, as I don't think there is an "out-of-the-box" solution in Qt to do everything you want to do. Good news is everything you want to do can indeed be done, but it will take time, especially if you are new. And I get the feeling it will be much easier to do from scratch with a QStandardItemModel and your own SQL statements than if you try to wrap around QSqlTableModel.
-
[quote author="jmelbye" date="1358702328"]http://qt-project.org/doc/qt-4.8/model-view-programming.html#note-content-13
[quote]When first starting to work with designing custom models based on QAbstractItemModel, QAbstractTableModel or QAbstractListModel, it is very tempting put your actual data storage inside the newly created model. While that technically works, it is generally not good practice to do so[/quote][/quote]
What I said is that the model must store the data it displays, not the ACTUAL or original data, the model data can still be fetched from whatever source you want. You cannot create an item without passing data to it, data that the item will store as a part of the model. Having the model data as THE DATA or primary data source is a bad idea indeed for various reasons, but the model must store the data it displays, there is no way around this.
-
[quote author="utcenter" date="1358702401"]@jmelbye - you seem to have the classical case of "unrealistic expectations" syndrome which is so often to see here[/quote]
Easy now. You and MuldeR have both outlined two approaches to accomplish what I have asked about. Both approaches seem like they will accomplish the job, and they each come with their own trade offs. I have stated that a design goal of mine is to keep the data in the table models. I would prefer "adapers" or "wrappers" around those to populating a second model with the same data.Your approach looks like it will be easier to get up and running, but I am hesitant to pursue it because my gut tells me there will be pains down the road keeping data in sync.
MuldeR's approach looks like it will require more effort up front, but in the long run, I suspect such an approach will make me happier.
[quote author="utcenter" date="1358702401"]To me it sounds like you need to build your own component, as I don't think there is an "out-of-the-box" solution in Qt to do everything you want to do. Good news is everything you want to do can indeed be done, but it will take time, especially if you are new.[/quote]
Based on the above suggestions, I agree. So now that is one decision I can put behind me. I will continue down this path.[quote author="utcenter" date="1358702401"]
And I get the feeling it will be much easier to do from scratch with a QStandardItemModel and your own SQL statements than if you try to wrap around QSqlTableModel.[/quote]
You may be correct, but I prefer the other route because I think in the long it will be easier to maintain.I do appreciate both of your suggestions. I am going to pursue the wrapper/adapter route outlined by MuldeR. It may take me some time, but I'll post my code when it is complete.
-
There is no need to be cocky ;) especially when it is obvious you clearly don't understand the matter of the problem, and when it comes to programming, I'd rather not trust my "gut" - it is not an issue of intuition but one of experience. As you don't seem to have all that much of it, if I were you I'd go for the simpler and easier approach, especially considering that if you go for the other, you will still have to do everything you have to do in the simpler one plus a bit more.
Here are a few things you didn't account for:
-
MuldeR's solution will not help you in any way to automatically keep sync between your SQL database and tree model as QSqlTableModel does, you will still have to do that yourself
-
wrapping around QSqlTableModel will most likely be much harder, because QSqlTableModel assumes tight interconnection with an SQL database, a lot of the tools that you will need to accomplish your task are absent from QSqlTableModel (e.g. there is no QSqlTableItem and so on) because that class was not intended to be worked with in that way. QStandardItemModel and QStandardItem on the other hand have a full API with everything you may need to accomplish your task.
-
QSqlTableModel still mirrors the SQL data, it is not the container of the data as you appear to think, just a wrapper to it, the data source is still the SQL database and QSqlTableModel still duplicates that in memory in order to show it
-
MuldeR's comment on my solution assumes the source to be other models, which I just used for the example, while also stating it could very well be a raw SQL database, in which case his argument about additional work to keep all models in sync is not valid because you don't need to do that, there are no multiple models to keep in sync, it is just the one tree model and the SQL database exactly as it would be in his solution, you will need the same amount of logic to do this as with his solution. His comment doesn't regard the sync between YOUR custom tree model and the SQL database in absolutely in any way
-
MuldeR's solution is to create your own "in memory" tree using a custom class - something that is hardly necessary, considering QStandardItemModel provides a full and rich API to build QStandardItem trees in a way that is "plug and play" compatible with a lot of Qt APIs. Also, your values are not limited to the members of the custom tree node class
-
Both solutions are not as fundamentally different as you appear to think. My solution is much simpler and relies on a mature Qt API that will save you work. Everything you'd need to do in the case of my approach you will have to do in case of MuldeR's approach plus a bit more. While more complex, his solution will do absolutely no magic for you, you will not escape the effort that is needed to keep your custom model in sync with the SQL database just like QSqlTableModel does. I stated that in the first point, but feel like it needs a little extra stressing, that is why I felt like closing with it as well. His solution is not about borrowing or reusing QSqlTableModel functionality, he offers a barebone tree subclassing and implementing your own model, something which you said you don't want to do, and something you really don't need to in that particular case. You still could, but it will take more effort which will not gain you anything.
Still, feel free to go with your gut, in both cases you are likely to do mistakes that can teach you :)
Also, I would encourage MuldeR to make any correction in case I made a false assumption on his behalf. But I think he will agree with the fact your major worries came from taking his comment out of its context.
-
-
I was able to write just one QAbstractItemModel subclass that wraps around an arbitrary number of child models. My class (TreeModelAdapter) is intended for composing a tree from table items (although I left part of the class definition generic enough to handle any QAbstractItemModel, no guarantees other types of child models work). Only one column of information is shown. Edit and insert work. I haven't implemented delete yet, but it will be pretty much a mirror of insert.
I've put together an example that implements the silly food model / editor I described above. Sorry about the UI, that wasn't my focus, it's a little rough.
Right click on a top level item to insert a new child
Double click on a child item to edit it
File->Save or Ctrl+S to save the item currently being edited
The example uses the QSQLITE db drive and tries to create a db file. Edit this to your liking in MainWindow::setupDb() if you don't have the QSQLITE driver or need to change the file path or something.Take a look at the code, you'll get it.
Its too big to post, you can grab a zip of the project here:
https://docs.google.com/file/d/0BwCeS6zygybVeVdjd2VXcjQwWk0/edit -
Thank you for sharing your solution and the example! Feel free to add it to the "Wiki":http://qt-project.org/wiki/, as it would be a pity if it get's lost here in the depth of General and Desktop.
This is (almost) excatly the same way as we build our tree tranformation model.
As it shows it is actually quite 'easy' to implement a transformation model based on QAbstractItemModel, especially a tree based one as there is no in-depth index calculation required. And the advantage is obvious: you can use any existing QAbstractItemModel, including QSqlRelationalTableModel, which already does the DBMS-related functionality (synchronization, caching, consistency).
We stick to a rule of thumb: if it exists in Qt, use it (like QSqlRelationalTableModel), because it is usually well-tested and bug-free - your own code probably isn't.
-
[quote author="Lukas Geyer" date="1358772323"]Thank you for sharing your solution and the example! Feel free to add it to the "Wiki":http://qt-project.org/wiki/, as it would be a pity if it get's lost here in the depth of General and Desktop.[/quote]
Thanks, I'll do that! I'm going to finish implementing delete and touch up the UI a bit, then I'll add it under the examples section.[quote author="Lukas Geyer" date="1358772323"]As it shows it is actually quite 'easy' to implement a transformation model based on QAbstractItemModel, especially a tree based one as there is no in-depth index calculation required.[/quote]
This did turn out to be easier than I thought it was going to be. When I began, I was storing an internal pointer with each model index that pointed to that same location in the internal tree representation. I was dreading insert and delete. It seemed redundant and error prone. Then it hit me that I never actually need a pointer to the item being examined. A parent and model index will always suffice. The child models do a much better job keeping track of their data than I do, so don't duplicate that! My implementation stores a pointer to an item's parent in the internal tree representation. The internal representation is then only two levels deep - the root item, and a parent item for each child model. That simplified things a lot.Once I wrap up my example and add it to the Wiki, I'll indicate such and mark this thread as solved.