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; }
|
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; } |
Part I: Introduction to C++ and Qt 4
C++ Introduction
Classes
Introduction to Qt
Lists
Functions
Inheritance and Polymorphism
Part II: Higher-Level Programming
Libraries
Introduction to Design Patterns
QObject
Generics and Containers
Qt GUI Widgets
Concurrency
Validation and Regular Expressions
Parsing XML
Meta Objects, Properties, and Reflective Programming
More Design Patterns
Models and Views
Qt SQL Classes
Part III: C++ Language Reference
Types and Expressions
Scope and Storage Class
Statements and Control Structures
Memory Access
Chapter Summary
Inheritance in Detail
Miscellaneous Topics
Part IV: Programming Assignments
MP3 Jukebox Assignments
Part V: Appendices
MP3 Jukebox Assignments
Bibliography
MP3 Jukebox Assignments