Display several separate table views as a tree
-
I have to believe that this is a somewhat common problem, but my search powers are failing me.
I have several database tables for different types of components in a system. I'd like to show all of the components in a tree view, grouped by component type. For example, a system for editing foods might have tables like:
@Fruitsid | name
1 | Apple
2 | Orange
3 | BananaVegetables
id | name
1 | Potato
2 | OnionMeats
id | name
1 | Beef
2 | Chicken@I would like to present these to the user in a tree view:
@[root]
|---Fruits
| |---Apple
| |---Orange
| |---Banana
|---Vegetables
| |---Potato
| |---Onion
|---Meats
|---Beef
|---Chicken@My application maintains models on the Fruits, Vegetables, and Meats database tables.
In general, I find tree models "hard". My current approach is to subclass QAbstractProxyModel and build internal representations of the flat table models and the tree structure, with row/column information to go from one to the other, and in the tree representation, pointers back to parent items. It seems like it will work, I'm able to initially load and present data to the user. But if feels messy and I really don't want to have to get into handling edits to the underlying models and updating the internal representations. I don't think my model/view know-how is good enough to do that correctly. (Currently I build the internal table and tree representations in the constructor, just to see if it will work, and have nothing implemented to handle inserts and deletes in the underlying table models).
So my question is, what suggestions do you have for showing several separate table models in a tree structure like this? Should I keep moving ahead with my current approach? I'd like to do as little subclassing as possible, relying instead on Qt's model implementations (which would be worlds better than anything I'll code up quickly). If subclassing is the answer, I'm probably over-complicating it.
Thanks!
-
I guess you'll have to implement your own QAbstractItemModel that acts as a wrapper around a QList of *QAbstractItemModel's. Though I think it won't be trivial to get all the QModelIndex's created/mapped right. Basically you'll have to re-implement all the standard QAbstractItemModel methods in your "wrapper" class and make them forward the individual call/request to the suitable "sub" model instance...
-
I was afraid of that. It surprises me how difficult this is. Might it be easier to emulate a tree then? Perhaps take several tree views, one for each table model, and stack them on top of each other in the UI? Visually I might be able to still accomplish the objective that way. I think I'll play with that approach some today.
-
Do you really need three separate QAbstractItemModel's? Wouldn't it be possible to create a very simple custom Tree data structure for your "raw" data? That plus a wrapper class derived from QAbstractItemModel that will wrap your custom Tree data structure into a QAbstractItemModel interface (so TreeView can use it). Once you have that, it should be straight forward to wrap each (sub) Tree separately into a QAbstractItemModel or combine them into a single Tree (simply by inserting one new root-node) and then wrap the combined tree.
I was thinking about something like:
@class MyTreeItem
{
public:
QString m_name;
QList<MyTreeItem*> m_children;
}@
@MyTreeItem *fruitsTree = new MyTreeItem();
fruitsTree.m_name = "Fruits";MyTreeItem *apple = new MyTreeItem();
apple.m_name = "Apple";
fruitsTree.m_children.append(apple);...@
@MyTreeWrapper wrapper1(fruitsTree);
ui->treeView1->setModel(wrapper1);MyTreeItem *combined = new MyTreeItem();
combined .m_name = "The Root";
combined .m_children.append(fruitsTree);
combined .m_children.append(vegetablesTree);
combined .m_children.append(meatsTree);MyTreeWrapper wrapper2(combined);
ui->treeView2->setModel(wrapper2);@ -
[quote author="MuldeR" date="1358696909"]Do you really need three separate QAbstractItemModel's?[/quote]
I have editor forms for each type of item presented in the tree. The user will be able to edit / insert / delete items. Therefore, I believe I'll want to have a QSqlTableModel for each table, no matter how I build the tree model/view.
[quote author="MuldeR" date="1358696909"] Wouldn't it be possible to create a very simple custom Tree data structure for your "raw" data? That plus a wrapper class derived from QAbstractItemModel that will wrap your custom Tree data structure into a QAbstractItemModel interface (so TreeView can use it). Once you have that, it should be straight forward to wrap each (sub) Tree separately into a QAbstractItemModel or combine them into a single Tree (simply by inserting one new root-node) and then wrap the combined tree.[/quote]
This is more or less the approach I'm currently taking. I find it quite difficult, maybe I just need more time to digest the model framework. In particular, distinguishing child from parent items, and being able to return a parent index for any child index seems to muck things up.
One thing about the approach you outlined different from what I'm currently doing is that you are wrapping each table into its own model, and building another model to wrap around all of them. I was trying to wrap a model around all of the QSqlTableModels at once. Your approach might simplify things a little.
[quote author="utcenter" date="1358697619"]You should be able to build a tree model of whatever you want, during the process of building a standard item model. Just use QStandardItem::appendRow to create a branch on a QStandardItem.[/quote]
Maybe I have a misconception, but in all the examples I've read on how to use a QStandardItemModel, the data is stored inside the model. I don't want this. I want the data store to be my sql database, and the models to be adapters around it.
I want to be able to make use of foreign key relationships as well. So my table models will really be QSqlRelationalTableModels. This detail is really only important for the editor forms. The tree view will just show the name field from each table. I mention it because it seems to add more uncertainty in my mind about how a QStandardItem-based approach would work.
-
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.