Background
Qt Provides a framework for separation of Data and View that show the data. This framework a simplified version of MVC, where controller embedded into View. Event after this separation it possible to display the same data in several different views, and to implement new types of views, without changing the underlying data structures.
Here model pulling data from the data source (files, databases or memory) & arrange the data according to a hierarchical structure. Views make the presentation of the data to user. The data editing is done through Delegate and again save it to Model. Model is responsible for saving data to data source.
Qt provided base classes for Model, View & Delegate are QAbstractItemModel, QAbstractItemView & QAbstractItemDelegate respectively. To use the existing models & views, it is very simple…
QFileSystemModel *pModel = new QFileSystemModel(); QTreeView *pTreeView = QTreeView(); pTreeView->setModel(pModel);
Lets gets on Models…
Models are responsible for pulling the data from data source. Arrange data following a hierarchical structure so that views/delegagte can get/set data (using an index called model index). Notify the View & Delegate while data has been modified. Actually there are three types of views & so as models — List, Table & Tree.
To ensure that the representation of the data is kept separate from the way it is accessed, the concept of a model index is introduced.
Qt Provided Ready-made Models
- List Model (QStringListModel)
QStringList names; lst <<"Sujon" <<"Roton"<<"Rahim"; QStringListModel *pModel = new QStringListModel(names,this); QListView *pView = new QListView(this); pView->setModel(pModel);
- Table Model (QSqlQueryModel, QSqlTableModel, QSqlRelationalTableModel)
QSqlTableModel *pModel = new QSqlTableModel; pModel->setTable("employee"); QTableView *pView = new QTableView; pView->setModel(pModel); pView->show();
- Tree Model: example given in the background section.
Creating a New Model
Think which type of model do you need? If you need List or Table type Model, consider to subclassing from QAbstractListModel or QAbstractTableModel. If it is tree type model, use QAbstractItemModel.
Subclassing List Model
- Read-Only Models: rowCount() and data().
- Editable Models: flags() and setData()
- Resizing Models: insertRows() and removeRows()
Subclassing Table Model
- Read-Only Models: rowCount(), columnCount() and data().
- Editable Models: flags() and setData()
- Resizing Models: insertRows(), removeRows(), insertColumn() and removeColumn()
Subclassing Tree Model
- Read-Only Models: index(), parent(), rowCount(),columnCount(), and data(). If rowCount() is expensive, it is better to override hasChildren().
- Editable Models: flags(), setData()
- Resizing Models: insertRows(), removeRows(), insertColumn(), removeColumn()
Example of subclassing List Model
class declaration…
class StringListModel : public QAbstractListModel { Q_OBJECT public: StringListModel(const QStringList &strings, QObject *parent = 0); // read only int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; // editable Qt::ItemFlags flags(const QModelIndex &index) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); // resizable bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()); bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()); private: QStringList mStringList; };
class definition…
StringListModel::StringListModel(const QStringList &strings,QObject *parent) : QAbstractListModel(parent),mStringList(strings) { } int StringListModel::rowCount(const QModelIndex &parent) const { return mStringList.count(); } QVariant StringListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= mStringList.size()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) return mStringList.at(index.row()); else return QVariant(); } QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) return QString("Column %1").arg(section); else return QString("Row %1").arg(section); } Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } bool StringListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { mStringList.replace(index.row(), value.toString()); emit dataChanged(index, index); return true; } return false; } bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent) { beginInsertRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { mStringList.insert(position, ""); } endInsertRows(); return true; } bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent) { beginRemoveRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { mStringList.removeAt(position); } endRemoveRows(); return true; }
Example of subclassing Tree Model
class declaration…
// We will use TreeItem class to represent an item in our Tree model. That is this class //is form the data structure of our Model class. class TreeItem { public: TreeItem(const QVector<QVariant> &data, TreeItem *parent = 0); ~TreeItem(); TreeItem *child(int number); int childCount() const; int columnCount() const; QVariant data(int column) const; bool insertChildren(int position, int count, int columns); bool insertColumns(int position, int columns); TreeItem *parent(); bool removeChildren(int position, int count); bool removeColumns(int position, int columns); int childNumber() const; bool setData(int column, const QVariant &value); private: QList<TreeItem*> childItems; QVector<QVariant> itemData; TreeItem *parentItem; }; // This is model class declaration class TreeModel : public QAbstractItemModel { Q_OBJECT public: TreeModel(const QStringList &headers, const QString &data, QObject *parent = 0); ~TreeModel(); QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; QModelIndex parent(const QModelIndex &index) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; Qt::ItemFlags flags(const QModelIndex &index) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); bool insertColumns(int position, int columns, const QModelIndex &parent = QModelIndex()); bool removeColumns(int position, int columns, const QModelIndex &parent = QModelIndex()); bool insertRows(int position, int rows, const QModelIndex &parent = QModelIndex()); bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()); private: void setupModelData(const QStringList &lines, TreeItem *parent); TreeItem *getItem(const QModelIndex &index) const; TreeItem *rootItem; };
And the definitions are…
// TreeItem class's definition TreeItem::TreeItem(const QVector<QVariant> &data, TreeItem *parent) { parentItem = parent; itemData = data; } TreeItem::~TreeItem() { qDeleteAll(childItems); } TreeItem *TreeItem::child(int number) { return childItems.value(number); } int TreeItem::childCount() const { return childItems.count(); } int TreeItem::childNumber() const { if (parentItem) return parentItem->childItems.indexOf(const_cast<TreeItem*>(this)); return 0; } int TreeItem::columnCount() const { return itemData.count(); } QVariant TreeItem::data(int column) const { return itemData.value(column); } bool TreeItem::insertChildren(int position, int count, int columns) { if (position < 0 || position > childItems.size()) return false; for (int row = 0; row < count; ++row) { QVector<QVariant> data(columns); TreeItem *item = new TreeItem(data, this); childItems.insert(position, item); } return true; } bool TreeItem::insertColumns(int position, int columns) { if (position < 0 || position > itemData.size()) return false; for (int column = 0; column < columns; ++column) itemData.insert(position, QVariant()); foreach (TreeItem *child, childItems) child->insertColumns(position, columns); return true; } TreeItem *TreeItem::parent() { return parentItem; } bool TreeItem::removeChildren(int position, int count) { if (position < 0 || position + count > childItems.size()) return false; for (int row = 0; row < count; ++row) delete childItems.takeAt(position); return true; } bool TreeItem::removeColumns(int position, int columns) { if (position < 0 || position + columns > itemData.size()) return false; for (int column = 0; column < columns; ++column) itemData.remove(position); foreach (TreeItem *child, childItems) child->removeColumns(position, columns); return true; } bool TreeItem::setData(int column, const QVariant &value) { if (column < 0 || column >= itemData.size()) return false; itemData[column] = value; return true; } // Model class's definition TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent) : QAbstractItemModel(parent) { QVector<QVariant> rootData; foreach (QString header, headers) rootData << header; rootItem = new TreeItem(rootData); setupModelData(data.split(QString("\n")), rootItem); } TreeModel::~TreeModel() { delete rootItem; } int TreeModel::columnCount(const QModelIndex & /* parent */) const { return rootItem->columnCount(); } QVariant TreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); TreeItem *item = getItem(index); return item->data(index.column()); } Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return 0; return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } TreeItem *TreeModel::getItem(const QModelIndex &index) const { if (index.isValid()) { TreeItem *item = static_cast<TreeItem*>(index.internalPointer()); if (item) return item; } return rootItem; } QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return rootItem->data(section); return QVariant(); } //! [5] QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) return QModelIndex(); TreeItem *parentItem = getItem(parent); TreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } bool TreeModel::insertColumns(int position, int columns, const QModelIndex &parent) { bool success; beginInsertColumns(parent, position, position + columns - 1); success = rootItem->insertColumns(position, columns); endInsertColumns(); return success; } bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent) { TreeItem *parentItem = getItem(parent); bool success; beginInsertRows(parent, position, position + rows - 1); success = parentItem->insertChildren(position, rows, rootItem->columnCount()); endInsertRows(); return success; } QModelIndex TreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TreeItem *childItem = getItem(index); TreeItem *parentItem = childItem->parent(); if (parentItem == rootItem) return QModelIndex(); return createIndex(parentItem->childNumber(), 0, parentItem); } bool TreeModel::removeColumns(int position, int columns, const QModelIndex &parent) { bool success; beginRemoveColumns(parent, position, position + columns - 1); success = rootItem->removeColumns(position, columns); endRemoveColumns(); if (rootItem->columnCount() == 0) removeRows(0, rowCount()); return success; } bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent) { TreeItem *parentItem = getItem(parent); bool success = true; beginRemoveRows(parent, position, position + rows - 1); success = parentItem->removeChildren(position, rows); endRemoveRows(); return success; } int TreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentItem = getItem(parent); return parentItem->childCount(); } bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role != Qt::EditRole) return false; TreeItem *item = getItem(index); bool result = item->setData(index.column(), value); if (result) emit dataChanged(index, index); return result; } bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (role != Qt::EditRole || orientation != Qt::Horizontal) return false; bool result = rootItem->setData(section, value); if (result) emit headerDataChanged(orientation, section, section); return result; }
Views
There are three types of views — List, Table & Tree. These are enough, but if you need a customize view, you can subclassing it.
Delegate
If you want to set just a control (widget), you may inherit QItemDelegate or QStyledItemDelegate and override createEditor() , setEditorData() and setModelData(). If you want to paint the control yourself, you need inherit QAbstractItemDelegate & also override paint(), sizeHint() in addition. Here is sample code that inheriting QItemDelegate.
// delegate codes QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &opt/* option */, const QModelIndex &mi/* index */) const { QSpinBox *editor = new QSpinBox(parent); QPoint range = GameSettings::me()->integerSettingRange(Game::Setting(mi.row())); editor->setMinimum(range.x()); editor->setMaximum(range.y()); return editor; } void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { //int value = index.model()->data(index, Qt::EditRole).toInt(); QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->setValue(GameSettings::me()->setting(Game::Setting(index.row())).value<uint>()); } void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->interpretText(); int value = spinBox->value(); model->setData(index, value, Qt::EditRole); } // Using that delegate QTableView *pTableView = new QTableView(); SpinBoxDelegate *pSpinboxDelegate = new SpinBoxDelegate(); pTableView->setItemDelegate(pSpinboxDelegate);
Proxy Model
If you want to sorting & filtering on the data, you need to use QAbstractProxyModel (inherited from QAbstractItemModel) descendant classes like QSortFilterProxyModel. If you decided to inherit QAbstractProxyModel , you must override mapFromSource() & mapToSource(). Here is the sample code on sorting & filtering…
TreeModel *pModel = new TreeModel(); QSortFilterProxyModel *pProxyModel = new QSortFilterProxyModel(); pProxyModel->setSourceModel(pModel); QTreeView *pView = new QTreeView(); pView->setModel(pProxyModel); // the following two lines will filter the column 1 for string "How to" pProxyModel->setFilterRegExp(QRegExp("How to", Qt::CaseInsensitive, QRegExp::FixedString)); pProxyModel->setFilterKeyColumn(1); // the following line will make view sortable. pView->setSortingEnabled(true);
The proxy model stands between the actual model & view in the model-view architecture. So the same sorting for same model will share among multiple views & only the sorting & filtering algorithm get execution only one time.
very clear tutorial. Thank you very much. I have a question for you. How to get selected item from QTableView? Thank you
Thanks for your comment.
You can use ‘selectedIndexes ‘ method to get all the selected items (actually you will get a list of selected index and then you can get info for each selected row/column). Or you can use ‘selectedRows’ from the underlying selection model.
But if you want to get informed when selection change, you can use ‘currentChanged ( const QModelIndex & current, const QModelIndex & previous )’.
Thank you, this post has shown me how to deal with Qt Model/View. I now intuit the forest behind the model, item, index and view trees.
Clear and focused. Altough understood those on-the-fly.
Great article!