Simple Tree Model Example▲
Qt's model/view architecture provides a standard way for views to manipulate information in a data source, using an abstract model of the data to simplify and standardize the way it is accessed. Simple models represent data as a table of items, and allow views to access this data via an index-based system. More generally, models can be used to represent data in the form of a tree structure by allowing each item to act as a parent to a table of child items.
Before attempting to implement a tree model, it is worth considering whether the data is supplied by an external source, or whether it is going to be maintained within the model itself. In this example, we will implement an internal structure to hold data rather than discuss how to package data from an external source.
Design and Concepts▲
The data structure that we use to represent the structure of the data takes the form of a tree built from TreeItem objects. Each TreeItem represents an item in a tree view, and contains several columns of data.
Simple Tree Model Structure The data is stored internally in the model using TreeItem objects that are linked together in a pointer-based tree structure. Generally, each TreeItem has a parent item, and can have a number of child items. However, the root item in the tree structure has no parent item and it is never referenced outside the model. Each TreeItem contains information about its place in the tree structure; it can return its parent item and its row number. Having this information readily available makes implementing the model easier. Since each item in a tree view usually contains several columns of data (a title and a summary in this example), it is natural to store this information in each item. For simplicity, we will use a list of QVariant objects to store the data for each column in the item. |
The use of a pointer-based tree structure means that, when passing a model index to a view, we can record the address of the corresponding item in the index (see QAbstractItemModel::createIndex()) and retrieve it later with QModelIndex::internalPointer(). This makes writing the model easier and ensures that all model indexes that refer to the same item have the same internal data pointer.
With the appropriate data structure in place, we can create a tree model with a minimal amount of extra code to supply model indexes and data to other components.
TreeItem Class Definition▲
The TreeItem class is defined as follows:
class
TreeItem
{
public
:
explicit
TreeItem(const
QVector&
lt;QVariant&
gt; &
amp;data, TreeItem *
parentItem =
nullptr
);
~
TreeItem();
void
appendChild(TreeItem *
child);
TreeItem *
child(int
row);
int
childCount() const
;
int
columnCount() const
;
QVariant data(int
column) const
;
int
row() const
;
TreeItem *
parentItem();
private
:
QVector&
lt;TreeItem*&
gt; m_childItems;
QVector&
lt;QVariant&
gt; m_itemData;
TreeItem *
m_parentItem;
}
;
The class is a basic C++ class. It does not inherit from QObject or provide signals and slots. It is used to hold a list of QVariants, containing column data, and information about its position in the tree structure. The functions provide the following features:
-
The appendChildItem() is used to add data when the model is first constructed and is not used during normal use.
-
The child() and childCount() functions allow the model to obtain information about any child items.
-
Information about the number of columns associated with the item is provided by columnCount(), and the data in each column can be obtained with the data() function.
-
The row() and parent() functions are used to obtain the item's row number and parent item.
The parent item and column data are stored in the parentItem and itemData private member variables. The childItems variable contains a list of pointers to the item's own child items.
TreeItem Class Implementation▲
The constructor is only used to record the item's parent and the data associated with each column.
TreeItem::
TreeItem(const
QVector&
lt;QVariant&
gt; &
amp;data, TreeItem *
parent)
:
m_itemData(data), m_parentItem(parent)
{}
A pointer to each of the child items belonging to this item will be stored in the childItems private member variable. When the class's destructor is called, it must delete each of these to ensure that their memory is reused:
TreeItem::
~
TreeItem()
{
qDeleteAll(m_childItems);
}
Since each of the child items are constructed when the model is initially populated with data, the function to add child items is straightforward:
void
TreeItem::
appendChild(TreeItem *
item)
{
m_childItems.append(item);
}
Each item is able to return any of its child items when given a suitable row number. For example, in the above diagram, the item marked with the letter "A" corresponds to the child of the root item with row = 0, the "B" item is a child of the "A" item with row = 1, and the "C" item is a child of the root item with row = 1.
The child() function returns the child that corresponds to the specified row number in the item's list of child items:
TreeItem *
TreeItem::
child(int
row)
{
if
(row &
lt; 0
||
row &
gt;=
m_childItems.size())
return
nullptr
;
return
m_childItems.at(row);
}
The number of child items held can be found with childCount():
int
TreeItem::
childCount() const
{
return
m_childItems.count();
}
The TreeModel uses this function to determine the number of rows that exist for a given parent item.
The row() function reports the item's location within its parent's list of items:
int
TreeItem::
row() const
{
if
(m_parentItem)
return
m_parentItem-&
gt;m_childItems.indexOf(const_cast
&
lt;TreeItem*&
gt;(this
));
return
0
;
}
Note that, although the root item (with no parent item) is automatically assigned a row number of 0, this information is never used by the model.
The number of columns of data in the item is trivially returned by the columnCount() function.
int
TreeItem::
columnCount() const
{
return
m_itemData.count();
}
Column data is returned by the data() function. The bounds are checked before accessing the container with the data:
QVariant TreeItem::
data(int
column) const
{
if
(column &
lt; 0
||
column &
gt;=
m_itemData.size())
return
QVariant();
return
m_itemData.at(column);
}
The item's parent is found with parent():
TreeItem *
TreeItem::
parentItem()
{
return
m_parentItem;
}
Note that, since the root item in the model will not have a parent, this function will return zero in that case. We need to ensure that the model handles this case correctly when we implement the TreeModel::parent() function.
TreeModel Class Definition▲
The TreeModel class is defined as follows:
class
TreeModel : public
QAbstractItemModel
{
Q_OBJECT
public
:
explicit
TreeModel(const
QString &
amp;data, QObject *
parent =
nullptr
);
~
TreeModel();
QVariant data(const
QModelIndex &
amp;index, int
role) const
override
;
Qt::
ItemFlags flags(const
QModelIndex &
amp;index) const
override
;
QVariant headerData(int
section, Qt::
Orientation orientation,
int
role =
Qt::
DisplayRole) const
override
;
QModelIndex index(int
row, int
column,
const
QModelIndex &
amp;parent =
QModelIndex()) const
override
;
QModelIndex parent(const
QModelIndex &
amp;index) const
override
;
int
rowCount(const
QModelIndex &
amp;parent =
QModelIndex()) const
override
;
int
columnCount(const
QModelIndex &
amp;parent =
QModelIndex()) const
override
;
private
:
void
setupModelData(const
QStringList &
amp;lines, TreeItem *
parent);
TreeItem *
rootItem;
}
;
This class is similar to most other subclasses of QAbstractItemModel that provide read-only models. Only the form of the constructor and the setupModelData() function are specific to this model. In addition, we provide a destructor to clean up when the model is destroyed.
TreeModel Class Implementation▲
For simplicity, the model does not allow its data to be edited. As a result, the constructor takes an argument containing the data that the model will share with views and delegates:
TreeModel::
TreeModel(const
QString &
amp;data, QObject *
parent)
:
QAbstractItemModel(parent)
{
rootItem =
new
TreeItem({
tr("Title"
), tr("Summary"
)}
);
setupModelData(data.split('
\n
'
), rootItem);
}
It is up to the constructor to create a root item for the model. This item only contains vertical header data for convenience. We also use it to reference the internal data structure that contains the model data, and it is used to represent an imaginary parent of top-level items in the model.
The model's internal data structure is populated with items by the setupModelData() function. We will examine this function separately at the end of this document.
The destructor ensures that the root item and all of its descendants are deleted when the model is destroyed:
TreeModel::
~
TreeModel()
{
delete