I'm writing an application in Qt (with C++) and I need to represent an object structure in a tree view. One of the ways to do this is to create a model for this, but I'm still quite confused after reading the Qt documentation about the subject.
The "structure" I have is pretty simple - there's a Project object that holds Task objects in a std::vector container. These tasks can also hold child tasks.
I've already written methods to read & write these projects to/from XML files using Qt's XML classes.
Is there any more documentation or "recommended reading" for creating models from scratch? How do you recommend I start implementing this?
As an alternative to what was said by Virgil in a comment to the question, you could use QStandardItemModel class for your model and just build your tree using this class. Below is an example:
QStandardItemModel* model = new QStandardItemModel();
QStandardItem* item0 = new QStandardItem(QIcon("test.png"), "1 first item");
QStandardItem* item1 = new QStandardItem(QIcon("test.png"), "2 second item");
QStandardItem* item3 = new QStandardItem(QIcon("test.png"), "3 third item");
QStandardItem* item4 = new QStandardItem("4 forth item");
model->appendRow(item0);
item0->appendRow(item3);
item0->appendRow(item4);
model->appendRow(item1);
ui->treeView->setModel(model);
When the UI (view) is destroyed, delete model. Documentation:
https://doc.qt.io/qt-5/qstandarditemmodel.html
https://doc.qt.io/qt-5/qstandarditem.html
The basic trick to get this working is really to get the model to data structure mapping right. Something that might seem hard, but needn't be.
First, using the QAbstractItemModel::createIndex to build model indexes, you can refer to your own data structure through the pointer or uint32 that you can add to the index, depending on which instance of createIndex that you choose to use.
Second, having the structure clear in mind (as you seem to have), it is quite easy to write the parent and index functions. The key here is to understand that the model root is an unintialized QModelIndex instance. I.e. QModelIndex::isValid() == false indicates root.
Third, if you go multi-column, remember that only the first column has children.
Fourth, to check that you do things the expected way, do use the ModelTest class. It monitors and checks your model, so that you follow the conventions that the Qt model view classes expect.
Related
In Qt I have a sqlite database which I'm pulling in. One of the tables (configTable) has a QSqlTableModel attached.
The table has a simple 2-column key/value structure. The keys are strings with folder-like values such as "general/name", "general/version", "foo/bar/baz", etc. Values are just arbitrary variants.
I'd like to display this data in an easier-to-browse QTreeView instead of a QTableView, as my key structure lends itself very nicely to that.
Before I go reimplementing classes and all sorts of crazy things - is there an elegant solution to this? And if I reimplement or extend classes, which ones should I look at?
Thank you.
You have to do the parsing+mapping between the list of value/value/value and a tree model yourself. But there is a (tricky) Qt way to do this yes.
The Qt Model-View architecture can represent many different structures of data, based on the QAbstractItemModel class. A Qt model must implement some functions to tell the view : how many columns, row, children etc.
A list model (Qt provides QAbstractListModel), is basically a model that says to the view :
I have one root item (all data items are represented by a QModelIndex, root has an invalid parent)
This root item has only one column
This root item has as many rows as your list has elements
A tree model will return the appropriate children for each QModelIndex. The abstract model of Qt actually allows each child item to be a table (QModelIndex always has a parent and a row-column index).
Long story short, you have to create a proxy model (QAbstractProxyModel or a suitable subclass, but for your need I don't think there is one). This proxy will transform the data your QSqlTableModel is sending, and this is where you can tell the view that you actually have a tree and not a list.
Your root items are the items from your database list of keys (first element of the foo/bar/whatever), but you need to regroup all the root items that has the same key.
AFAIK you can make it only manually.
Basically, because how did you think Qt knows how to convert your data into tree model.
I have a list with about 2500 custom items. I set them with:
const std::vector<const Items::AbstractItem *> results = _engine.request(text);
if (!results.empty())
{
for (auto i : results){
QListWidgetItem *lwi = new QListWidgetItem;
_results->addItem(lwi);
ListItemWidget *w = new ListItemWidget;
w->setName(i->name());
w->setTooltip(i->path());
_results->setItemWidget(lwi, w);
}
_results->setFixedHeight(std::min(5,_results->count()) * 48); // TODO
_results->show();
}
This takes about 5 seconds on an i5-4590. Hiding the widget is twice as fast. Is this normal or do I have look for errors?
A few ideas:
Try assigning proper parents to your QWidgets, thats way the layout doesn't have to do this
mapping for you. This should help performance.
Call setUpdatesEnable(false) before starting the insert, and to true after it's done
As for hiding the widget while adding large amounts of items, this will help to alleviate extraneous update calls. The second suggestion above should mitigate that.
I think this is fully expected behavior for controls like Lists or Trees that are not based on any data model. And I believe that the data model was invented mainly to fix this issue.
In your situation you have a ListWidget control that stores its data on its own. You need to pass all 2500 items before your app can go on, and you need to do this even if your list shows only 10 items at a time. Even if you just run and close your app, the user won't see all the items but you still need to pass them to your ListWidget. Some GUI frameworks use internal allocation of items and in such case they can optimize things a bit, you could do the same if you allocated your Items in chunks but it's still not a good solution.
Now let's say you introduce some object that could be asked about item properties. The Control will ask about some item and your object will respond with the contents. Your object don't even need to know about all your items, it will just learn when needed.
You Control can ask about few first items and stop when it realize it can fill up its entire height. This way you can avoid work that is not needed for now. The Control can also ask about the item count, so it can set-up its vertical slider.
It needs to be said that the model will not solve your problem automatically, it's just a programming paradigm that allows you to do it better.
So the solution for you would be to replace your QListWidget with a QListView and implement you own data model inheriting QAbstractListModel. You could pass the results to the model and it will pass the items data when needed.
If your QListWidgetItem's always has fixed size, call setUniformItemSizes on your QListWidget, pass true.
First I want to thank you in advance for your answers. I insert one QStandardItem in one QStandardItemModel and then display this model in a Tree View (A). After this I append the same item (pointer) to a new QStandardItemModel wich is associated with another Tree View (B). It is only displayed one empty item instead one item with the same text in this second case. If I make a copy of the item, the result the expected. Why can I not append the same item in two different models?
In this case is not useful to use QSortFilterProxyModel because modelA and modelB have the same data but following a very different structure.
Thank you very much.
If it is not possible to do this, which solution you suggest me? I though maintaining a correspondence between table models by using hash tables, but I think there is a easier solution.
I copy one code example.
QStandardItem * item = generateExampleItem();
modelA->invisibleRootItem()->appendRow(item); // will be visible to the user
modelB->invisibleRootItem()->appendRow(item); // will be invisible to the user
ui.treeViewA->setModel(modelA);
ui.treeViewB->setModel(modelB);
ui.treeViewA->show();
ui.treeViewB->show();
I have a little problem I am trying to figure out, I am working on a QT app that is using the QTreeView and I have a bunch of categories which have children, so they look like
Parent 1
Parent 2
- Child 1
- Child 2
Parent 3
and so on and so forth, so in my database I have rows which have all the regular details (name, id, date created, etc) and the ones which are children have their parent specified (so pid=parent row id). I then loop over then using the standard QSqlQuery stuff. But the problem I am running into is this...
Items are added to the treeview by QStandardItem* item = new QStandardItem(icon, name); and then appending the row model->appendRow(item); but my children need to call parentitem->appendRow(item); so the QStandardItem* item of the parent. But how can I find out what that is without storing every single item?
Moral of the story is, is there a way to do one of the following that won't destroy performance.
Store the QStandardItem* item in an array that I could reference the parent in the childs loop?
Assign an ID or something to QStandardItem* item which I could then reference when adding a child.
Generate a TreeView model from an array, where the children array elements get added as children?
Something else I haven't thought of...
I can't seem to find any good examples of QTreeView with children from a database.
All you need is a QMap<int, QStandardItem*> rowItemMap. When you retrieve a row from the database with a given row id, you immediately create an item and add it to the map. You then add it to a parent that you look up in the map. You'll need to create a dummy parent as the root item. There's nothing wrong with storing pointers to items. For reasonable amounts of items, it won't matter. If you think of storing more than 10k items, you may want to think of using a view that offers transitive closure up to a certain depth of the tree. It'd then be much easier to map such a view, via QSqlTableModel, directly onto the tree, without having to copy the entire tree from the database into a temporary model.
I have something like this:
Order order = new Order();
Item item = new Item();
order.Items.Add(item);
order.Save();
How can I do this with Subsonic? The method that refere to a related table is IQueryable.
You have three options:
Set the foreign key in Item to the id of your Order object and save both.
Create a partial class which has a method "AddItem", encapsulating this functionality
Modify the T4 templates to allow you to do this automatically; unfortunately this feature doesn't come out of the box yet.
The advantage with Subsonic is that it is flexible, however you occasionally have to fill some of the gaps yourself.
If you are programming something like a shopping cart you can abstract that out into it's own class that can handle marrying the objects together. I personally think it works better than modify the generated objects.