QSqlQueryModel and QTreeView - part 2

Submitted by mimec on 2011-06-27

In the previous post I described how to combine multiple QSqlQueryModels in order to create a hierarchy of items which can be displayed in a QTreeView.

Our class (let's call it SqlTreeModel) will inherit QAbstractItemModel and therefore must implement at least the five pure virtual methods: columnCount, rowCount, index, parent and data. The implementation of rowCount, index and parent is based on the internal data structures which I described in the previous article. Those methods are used to enumerate items and navigate back and forth within the tree. They are not very complex and I will not describe them here in detail; you can peek into the code of the WebIssues client if you are interested.

The data method is also pretty straightforward; it determines the level of the item and delegates the implementation to the model associated with the level. It also maps the row and column from the tree model to the underlying SQL query model. Mapping of rows is based on the internal data structures, which I described previously.

How columns are mapped depends on what is needed in a particular situation. In the simplest case, they could be simply mapped one-to-one to the underlying SQL models; the first column of the tree would correspond to the first column in every SQL query model (after skipping identifiers used to build relations between parent and child items), and so on. So the number of columns in the tree view (as returned by columnCount) would be determined by the largest number of columns in all dependent models.

That's how SqlTreeModel works by default, but it also allows customizing this default column mapping. Let's suppose that we want to display a different icon depending on the state of the item. The state can be retrieved from the database as a separate column in the SQL query model. However we don't want this column to be displayed directly in the view, but instead its values should be used to modify data (in this case, the icon) of another column.

This can be done by inheriting the SqlTreeModel and reimplementing the data virtual method. When retrieving the decoration of the specific column at the specific level, it would ask for the value of the appropriate column in the SQL query model and return the appropriate icon. In other cases, it would call the default implementation. We also need to customize column mapping, so that this extra column, containing the state of the item, is not displayed. The column mapping can be simply a list of indexes in the SQL query model that correspond to subsequent columns in the tree model.

The value of a specific column can also be calculated using some algorithm based on several source columns or an external data source. In that case we can map it to a placeholder (represented by a negative value) instead of a specific column in the underlying model.

Yet another problem related to handling columns is retrieving column headers. We can delegate the headerData virtual method to one of the child SQL query models, but with different number of columns and various mappings, this can quickly become difficult to manage. I took a simpler approach - I simply implemented setHeaderData and headerData so that they store all passed values, just like the QSqlQueryModel does. So after adding all child SQL models and setting up column mappings, we can simply set column headers by calling setHeaderData for each column.

It's getting complicated, isn't it? In the next post I will describe how to handle sorting and how to update the model without losing the state of expanded and selected nodes in the tree view associated with our model.