Software development challenges around index creation for data models
-
Qt data models are using indexes. Appropriate algorithms need to be chosen then.
The data type “int” is used for the specification of columns and rows. This technical detail can trigger software development considerations for the portable value range of signed integers.
- Smaller components can be combined into the integer data type for the final index usage.
Can the Qt software library help any more here? - Another programming interface is described for hierarchical models.
I find this API not so obvious for the implementation of an index generation algorithm for a customised data model.
One implementation is provided for a class like “QStandardItemModel”. Will it become reusable also by other means than derivation?
- Smaller components can be combined into the integer data type for the final index usage.
-
@elfring said in Software development challenges around index creation for data models:
This technical detail can trigger software development considerations for the portable value range of signed integers.
Yes but not really. Qt only supports platforms where
int
is 4 bytes of 8 bitsSmaller components can be combined into the integer data type for the final index usage.
What do you have in mind?
I find this API not so obvious for the implementation
I agree it's not intuitive but once you get accustomed to it it works fine.
-
What do you have in mind?
I imagine that an index value can be computed for a simple hierarchical model by using a formula like “
((X & 0x0FFF) << 16) + (Y & 0xFFFF)
”.I agree it's not intuitive …
Thanks for your acknowledgement.
I hope that this software situation can be improved somehow.
-
@elfring said in Software development challenges around index creation for data models:
I imagine that an index value can be computed for a simple hierarchical model by using a formula like “((X & 0x0FFF) << 16) + (Y & 0xFFFF)”
In some specific cases yes but not in general
I hope that this software situation can be improved somehow.
Nope. The concept is very similar to how xml DOM works so it's a solid design pattern that doesn't need changing. not intuitive != wrong.
-
it's a solid design pattern
Index usage can be generally fine.
that doesn't need changing. not intuitive != wrong.
- Can the desired index calculations become easier to reuse for customised data models?
- Would you like to offer any known indexing approaches directly?
-
@elfring said in Software development challenges around index creation for data models:
Would you like to offer any known indexing approaches directly?
struct ModelItem{ ModelItem* parent = nullptr; QList<QList<ModelItem*> > children; QMap<int,QVariant> data; }
-
@elfring said in Software development challenges around index creation for data models:
You have shown another example for a data structure.
No, that is a hierarchical level. Something like the below:
UNTESTED CODE!
class GenericModel : public QAbstractItemModel{ Q_DISABLE_COPY(GenericModel) struct ModelItem{ ModelItem* parent = nullptr; QVector<QVector<ModelItem*> > children; QMap<int,QVariant> data; ModelItem(ModelItem* par) :parent(par) {} ~ModelItem(){ for(auto i=children.begin();i!=children.end();++i){ for(auto j=i->begin();j!=i->end();++j) delete *j; } } }; public: explicit GenericModel(QObject* parent = nullptr) : QAbstractItemModel(parent) , rootItem(new ModelItem(nullptr)) {} ~GenericModel() { delete rootItem;} QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override{ Q_ASSERT(checkIndex(parent)); if(parent.isValid()) return createIndex(row,column,itemForIndex(parent)); return createIndex(row,column, rootItem); } QModelIndex parent(const QModelIndex &index) const override{ Q_ASSERT(checkIndex(index, CheckIndexOption::DoNotUseParent)); return indexForItem(static_cast<ModelItem*>(index.internalPointer())); } int rowCount(const QModelIndex &parent = QModelIndex()) const override{ Q_ASSERT(checkIndex(parent)); if(!parent.isValid()) return rootItem->children.size(); return itemForIndex(parent)->children.size(); } int columnCount(const QModelIndex &parent = QModelIndex()) const override{ Q_ASSERT(checkIndex(parent)); const ModelItem* const item =parent.isValid() ? itemForIndex(parent) : rootItem; Q_ASSERT(item); if(item->children.isEmpty()) return 0; return item->children.at(0).size(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override{ Q_ASSERT(checkIndex(index)); const ModelItem* const item = itemForIndex(index); if(!item) return QVariant(); return item->data.value(role); } bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override{ Q_ASSERT(checkIndex(index)); ModelItem* const item = itemForIndex(index); if(!item) return false; //inefficient but works item->data[role] = value; dataChanged(index,index,{role}); return true; } bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override{ if(row<0 || row>rowCount(parent) || count<=0) return false; ModelItem* const parItem = parent.isValid() ? itemForIndex(parent) : rootItem; const int colCount = columnCount(parent); beginInsertRows(parent,row,row+count-1); while(count--){ QVector<ModelItem*> colVector(colCount); for(int i=0;i<colCount;++i) colVector[i]=new ModelItem(parItem); parItem->children.insert(row, colVector); } endInsertRows(); return true; } bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override{ if(column<0 || column>columnCount(parent) || count<=0) return false; ModelItem* const parItem = parent.isValid() ? itemForIndex(parent) : rootItem; const int rowCnt = rowCount(parent); if(rowCnt==0) return false; //can't insert columns if no rows are there beginInsertColumns(parent,column,column+count-1); while(count--){ for(int i=0;i<rowCnt;++i) parItem->children[i].insert(column, new ModelItem(parItem)); } endInsertColumns(); return true; } private: ModelItem* itemForIndex(const QModelIndex& idx) const { if(!checkIndex(idx, CheckIndexOption::IndexIsValid)) return nullptr; const ModelItem* const parentItem = static_cast<ModelItem*>(idx.internalPointer()); return parentItem->children.at(idx.row()).at(idx.column()); } QModelIndex indexForItem(const ModelItem* const itm) const { ModelItem* const parentItem = itm->parent; if(!parentItem) return QModelIndex(); for(int rowIter = 0, maxRow = parentItem->children.size(); rowIter<maxRow;++rowIter){ for(int colIter = 0, maxCol = parentItem->children.at(rowIter).size(); colIter<maxCol;++colIter){ if(parentItem->children.at(rowIter).at(colIter) == itm) return createIndex(rowIter,colIter,parentItem); } } Q_UNREACHABLE(); return QModelIndex(); } ModelItem* rootItem; };
-
@elfring said in Software development challenges around index creation for data models:
Do you distinguish any levels in the object hierarchy of a data model?
You can if you want. Instead of making
ModelItem
a struct, use it as an interface and subclass it to create different type of items if you need -
@elfring said in Software development challenges around index creation for data models:
How would you map a data model to Qt programming interfaces when each level within a hierarchy should correspond to a specific class?
Explain what you mean by that.
-
In this case I'd use a generic item that supports
QVariant
and store the class in it as data.
An alternative is to have an item with avoid*
(or a common base class if available) containing the class instance and anint
that keeps track of what type that pointer is holding so it can bedynamic_casted
when needed -
Explain what you mean by that.
Will the following hierarchy example help for a better common understanding of a possible data model?
- Directories contain files.
- Text files can contain several lines.
- Text lines contain characters.
Which classes would you like to use then in your software application?
-
The model infra is abstract enough so you can attach whatever you want to it. What you're describing is (kind of) a file system model with additional tweaks so you can subclass it and implement the last part. Or you can start from scratch and implement your own if you like.
For my current project I use the model simply as a proxy to an abstract class that holds the data (due to various reasons), so you can do that either if you like. The options are limitless ...
-
I'd use the QVariant approach
I am occasionaly trying to avoid the data transfer by such a generic class.
Will it make sense to apply an other software design composition?
- Can it make sense to map even simple hierarchy levels to separate model classes?
- Should relationships between model instances be expressed separately?
-
What you're describing is (kind of) a file system model
Did anybody try to represent data as a file system for model variants besides the usage of the class “QFileSystemModel”?
with additional tweaks so you can subclass it and implement the last part.
I am trying again to clarify corresponding software development possibilities.
For my current project I use the model simply as a proxy to an abstract class that holds the data …
This design approach sounds very promising. How should data accesses be redirected to the existing container object here?
-
@elfring said in Software development challenges around index creation for data models:
This design approach sounds very promising. How should data accesses be redirected to the existing container object here?
MyModel::MyModel(QObject * parent) : QAbstractTableModel(parent), dataSource(nullptr) { } void MyModel::setDataSource(MyDataSource * source) { if (dataSource) { QObject::disconnect(this, nullptr, dataSource, nullptr); QObject::disconnect(dataSource, nullptr, this, nullptr); } dataSource = source; QObject::connect(dataSource, &MyDataSource::dataChangeStarted, this, &MyModel::beginResetModel); QObject::connect(dataSource, &MyDataSource::dataChangeFinished, this, &MyModel::endResetModel); } QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal || section >= dataSource->columnCount()) return QVariant(); Q_ASSERT(dataSource); return dataSource->columnName(section); } int MyModel::rowCount(const QModelIndex &) const { Q_ASSERT(dataSource); return dataSource->rowCount(); } int MyModel::columnCount(const QModelIndex &) const { Q_ASSERT(dataSource); return dataSource->columnCount(); } QVariant MyModel::data(const QModelIndex & index, int role) const { Q_ASSERT(dataSource); if (!index.isValid() || role != Qt::DisplayRole) return QVariant(); return dataSource->value(index.row(), index.column()); }
and of course the corresponding interface:
class MyDataSource : public QObject { Q_OBJECT Q_DISABLE_COPY(MyDataSource) public: MyDataSource(QObject * = nullptr); virtual int rowCount() const = 0; virtual int columnCount() const = 0; virtual QString columnName(int) const = 0; virtual QVariant value(int, int) const = 0; virtual QString format(const QVariant &, int) const; signals: void changed(); void dataChangeStarted(); void dataChangeFinished(); protected slots: virtual void reloadData() = 0; };