Table Models
It would be useful to have an editable QTableView for viewing and editing a collection of DataObjects. For this, we extend and customize the QAbstractTable Model. See Example 17.17.
Example 17.17. src/libs/dataobjects/dataobjecttablemodel.h
[ . . . . ] class DataObjectTableModel : public QAbstractTableModel { Q_OBJECT public: DataObjectTableModel(DataObject* headerModel = 0); virtual DataObject* record(int rowNum) const ; virtual bool insertRecord(DataObject* newRecord, int position = -1, const QModelIndex& = QModelIndex()); QStringList toStringList() const; QString toString() const; virtual int fieldIndex(QString fieldName) const; virtual ~DataObjectTableModel(); [ . . . . ] public slots: void reset(); void checkDirty(); protected slots: void changeProperty(const QString&, const QVariant&); protected: QList m_Data; QStringList m_Headers; DataObject* m_Original; QTimer m_Timer; bool m_Dirty; void extractHeaders(DataObject* hmodel); public: DataObjectTableModel& operator<<(DataObject* newObj) { insertRecord(newObj); return *this; } }; |
The name, DataObjectTableModel, is quite self-descriptive: a table of DataObjects. Using this class to group DataObjects makes creating editable views reasonably simple. Example 17.18 shows the client code that produced the screen-shot above.
Example 17.18. src/modelview/tablemodel/tablemodel.cpp
#include "dataobjecttablemodel.h" #include "customerfactory.h" #include "country.h" DataObjectTableModel* model() { CustomerFactory* fac = CustomerFactory::instance(); Customer* cust1 = fac->newCustomer("luke skywalker", Country::USA); DataObjectTableModel* retval = new DataObjectTableModel(cust1); <-- 1 cust1->setId("14123"); *retval << cust1; <-- 2 *retval << fac->newCustomer("Ben Kenobi", Country::Canada); *retval << fac->newCustomer("Princess Leia", Country::USA); return retval; } #include #include #include #include int main(int argc, char** argv) { QApplication app(argc, argv); DataObjectTableModel *mod = model(); QMainWindow mainwin; QTableView view ; view.setModel(mod); mainwin.setCentralWidget(&view); mainwin.setVisible(true); int retval = app.exec(); qDebug() << "Application Exited. " << endl; qDebug() << mod->toString() << endl; delete mod; return retval; } (1)header model (2)Insert row into table. |
In the public interface, we want convenient functions for operating on rows as DataObject records. See Example 17.19.
Example 17.19. src/libs/dataobjects/dataobjecttablemodel.cpp
[ . . . . ] bool DataObjectTableModel:: insertRecord(DataObject* newRow, int position, const QModelIndex &parent) { if (position==-1) position=rowCount()-1; connect (newRow, SIGNAL(propertyChanged(const QString&, const QVariant&)), this, SLOT(changeProperty(const QString&, const QVariant&))); beginInsertRows(parent, position, position); m_Data.insert(position, newRow); endInsertRows(); return true; } DataObject* DataObjectTableModel:: record(int rowNum) const { return m_Data.at(rowNum); } |
But How Does It Work?
QAbstractTableModel has a series of pure virtual functions, declared in Example 17.20, which must be overridden, because they are invoked by QTableView to get and set data.
Example 17.20. src/libs/dataobjects/dataobjecttablemodel.h
[ . . . . ] /* Methods which are required to be overridden because of QAbstractTableModel */ int rowCount(const QModelIndex& parent = QModelIndex()) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = DisplayRole) const; ItemFlags flags(const QModelIndex &index) const; bool setData(const QModelIndex &index, const QVariant &value, int role = EditRole); bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()); bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()); |
Example 17.21 shows the methods used to get data in and out of the model.
Example 17.21. src/libs/dataobjects/dataobjecttablemodel.cpp
[ . . . . ] QVariant DataObjectTableModel:: data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (role == DisplayRole) { int row(index.row()), col(index.column()); DataObject* lineItem(m_Data.at(row)); return lineItem->property(m_Headers.at(col)); } else return QVariant(); } bool DataObjectTableModel:: setData(const QModelIndex &index, const QVariant &value, int role) { bool changed=false; if (index.isValid() && role == EditRole) { int row(index.row()), col(index.column()); DataObject* lineItem(m_Data.at(row)); changed = lineItem->setProperty(m_Headers.at(col), value); if(changed) emit dataChanged(index, index); } return changed; } |
This is a mapping layer from objects to tables. Since the tables need to show header data, the table model has one DataObject, designated the header model, which it uses to obtain headers. Example 17.22 defines headerData, the method that table models call, which we can override to provide the proper header names.
Example 17.22. src/libs/dataobjects/dataobjecttablemodel.cpp
[ . . . . ] QVariant DataObjectTableModel:: headerData(int section, Qt::Orientation orientation, int role) const { if (role != DisplayRole) return QVariant(); if(orientation == Qt::Vertical) return QVariant(section); if (m_Headers.size() ==0) return QVariant(); return m_Headers.at(section); } int DataObjectTableModel::rowCount(const QModelIndex&) const { return m_Data.count(); } int DataObjectTableModel:: columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_Headers.size(); } |
The other methods in the abstract interface, shown in Example 17.23, tell views which items are editable (flags()), or allow client code to insert and remove rows.
Example 17.23. src/libs/dataobjects/dataobjecttablemodel.cpp
[ . . . . ] ItemFlags DataObjectTableModel:: flags(const QModelIndex &index) const { if (!index.isValid()) return ItemIsEnabled; // TODO - check the metaProperty to see if it is read/write return QAbstractItemModel::flags(index) | ItemIsEditable; } void DataObjectTableModel:: reset() { QModelIndex br = index(rowCount()-1, columnCount()-1); QModelIndex tl = index(0,0); emit dataChanged(tl, br); m_Dirty = false; } bool DataObjectTableModel:: insertRows(int position, int rows, const QModelIndex &parent) { beginInsertRows(parent, position, position+rows-1); for (int row = 0; row < rows; ++row) { DataObject* dobj = m_Original->clone(); m_Data.insert(position, dobj); } endInsertRows(); return true; } bool DataObjectTableModel:: removeRows(int position, int rows, const QModelIndex& parent) { for (int row = 0; row < rows; ++row) { delete m_Data.at(position); m_Data.removeAt(position); } QModelIndex topLeft(index(position, 0, parent)); QModelIndex bottomRight(index(position + 1, columnCount(), parent)); emit dataChanged(topLeft, bottomRight); return true; } |