13.4 Tree Models
To display trees of data in a QTreeView (parents and children), you have a few options:
- QAbstractItemModel is a general-purpose abstract model used with QTreeView, QListView, or QTableView.
- QStandardItemModel, used in Example 13.2, is a concrete class that can store QStandardItems, making it convenient to populate a concrete model with tree nodes.
- QTreeWidgetItem is not a model class, but it can build trees in a QTreeWidget, derived from QTreeView.
The QStandardItemModel and QTreeWidgetItem classes are tree nodes that can be instantiated or extended. The individual objects connect in a tree-like fashion, similar to QObject children (Section 8.2) or QDomNodes (Section 15.3). In fact, these classes are implementations of the Composite pattern.
Figure 13.12 is a screenshot of the next example, which shows the objects currently in memory that compose the user interface of the application.
Figure 13.12 ObjectBrowser Tree
The class definition for this application is shown in Example 13.21. ObjectBrowserModel is a concrete tree model extended from QAbstractItemModel. It implements all the necessary methods to provide a read-only object browser tree.
Example 13.21. src/modelview/objectbrowser/ObjectBrowserModel.h
[ . . . . ] #include <QAbstractItemModel> class ObjectBrowserModel :public QAbstractItemModel { public: explicit ObjectBrowserModel (QObject* rootObject); int columnCount ( const QModelIndex& parent = QModelIndex() ) const; int rowCount ( const QModelIndex& parent = QModelIndex() ) const; QVariant data ( const QModelIndex& index, int role = Qt::DisplayRole ) const; QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const; QModelIndex index ( int row, int column, const QModelIndex& parent = QModelIndex()) const; QModelIndex parent ( const QModelIndex& index ) const; protected: QList<QObject*> children( QObject* parent ) const; QString label( const QObject* widget, int column ) const; QObject* qObject( const QModelIndex& ) const; private: QObject *rootItem; }; [ . . . . ]
To enable tree views to navigate up and down the parent-child hierarchy of a QAbstractItemModel, you need to implement two methods that were not needed for table views: index() and parent(). Example 13.22 shows their implementation.
Example 13.22. src/modelview/objectbrowser/ObjectBrowserModel.cpp
[ . . . . ] QModelIndex ObjectBrowserModel:: index(int row, int col, const QModelIndex& parent) const { if ((row < 0) || (col < 0) || row >= rowCount() || col >= columnCount()) return QModelIndex(); return createIndex( row, col, qObject(parent) );1
} QModelIndex ObjectBrowserModel::parent( const QModelIndex& index ) const { if (!index.isValid()) return QModelIndex(); QObject* obj = qObject(index)->parent();2
if ( obj == 0 ) return QModelIndex(); QObject* parent = obj->parent(); int row = children( parent ).indexOf( obj ); return createIndex( row, 0, parent ); } QObject* ObjectBrowserModel:: qObject(const QModelIndex& index) const {3
if ( index.isValid() ) { QObject* parent = reinterpret_cast<QObject*>( index.internalPointer() ); return children(parent)[index.row()];4
} return 0;5
}
|
Store an internalPointer in the index. |
|
qObject() returns the row child of this index, but you want this index's parent QObject pointer, which is stored in index.internalPointer(). |
|
My index's internalPointer is my parent QObject pointer. I am the row() child of my parent. |
|
This is me! |
|
This is the root. |
index() can also be thought of as "childIndex," as it is used to find QModelIndex of children in the tree, while parent() calculates one step in the other direction. It is not important to implement these methods in table models that are only used with table views, but index() and parent() are necessary if you ever want to view the model with a tree view.
13.4.1 Trolltech Model Testing Tool
The methods you implement in a model are called by views you may not have tested or use cases that you have not tried. Because the data values are often driven by the user, it can be helpful to test your model with the "modeltest" tool. It finds common implementation errors quickly and gives you an indication of how to fix them.
Figure 13.13 shows what happens when you run the ObjectBrowser example from Example 13.21 in the debugger from QtCreator, with the ModelTest tool. The failed assertions and aborts might be deeper in the stack trace, so you need a debugger to see the full stack trace. Typically, there will be code comments right before the aborted line that indicate what test it was performing at that point. The included readme.txt file, shown in Example 13.23, contains brief instructions for using ModelTest.
Figure 13.13 ModelTest in the Debugger
Example 13.23. src/libs/modeltest/readme.txt
To use the model test do the following: 1) Include the pri file at the end of your project pro file using the include() command like so: include(../path/to/dir/modeltest.pri) 2) Then in your source include "modeltest.h" and instantiate ModelTest with your model so the test can live for the lifetime of your model. For example: #include <modeltest.h> QDirModel *model = new QDirModel(this); new ModelTest(model, this); 3) That is it. When the test finds a problem it will assert. modeltest.cpp contains some hints on how to fix problems that the test finds.