📜 ⬆️ ⬇️

Qt Model Tricks

Hello!
In this short article I will teach you one interesting trick with models that can be implemented using Qt's MVC framework.

Baseline data for the trick.


Two-level tree model:
|Parent 1 -----Child 1 -----Child N |Parent N -----Child 1 -----Child N 


List Model:
 Item1 Item2 Item3 

')
As a result of the trick, we will get a model combining the two models above:
 |Parent 1 ------Child 1 ------Child N |Parent N ------Child 1 ------Child N |Item1 |Item2 |Item3 


Let's start the implementation.


And so how to do it? I think you already guessed that you can do this by using QAbstractProxyModel. And no! Unfortunately, the standard QAbstractProxyModel class can convert only one source model (which is also not bad). Therefore, we will write our ModelJoinerProxy, which will compose our two source models into one.
And so proceed:

 //  QAbstractItemModel     //   Qt class ModelJoinerProxy : public QAbstractItemModel { Q_OBJECT public: ModelJoinerProxy(QObject *parent = 0); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; QModelIndex parent(const QModelIndex &child) const; int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; Qt::ItemFlags flags(const QModelIndex &index) const; //     1   virtual void setSourceModel1(QAbstractItemModel *sourceModel1); //     2   virtual void setSourceModel2(QAbstractItemModel *sourceModel2); //         virtual QModelIndex mapToSource(const QModelIndex &) const; //         virtual QModelIndex mapFromSource(const QModelIndex &) const; private slots: void source_dataChanged(QModelIndex, QModelIndex); void source_rowsAboutToBeInserted(QModelIndex p, int from, int to); void source_rowsInserted(QModelIndex p, int, int); void source_rowsAboutToBeRemoved(QModelIndex, int, int); void source_rowsRemoved(QModelIndex, int, int); void source_modelReset(); private: QAbstractItemModel *m1; QAbstractItemModel *m2; }; 


Our mediator model is a two-tier tree model, to achieve this we
We redefine index (..) and parent (..) as if we are building a model of a regular tree.
Next we need our model to have the correct number of rows and columns,
(for simplicity, the number of columns in the original models and the intermediary model will be = 1)
for this we override rowCount (....) and columnCount (.....).

 int ModelJoinerProxy::rowCount(const QModelIndex &parent) const { int count = 0; //1  if (!parent.isValid()) count = m1->rowCount() + m2->rowCount(); //2  else if (parent.internalId() == -1) { //            m1 , //         if ( parent.row() < m1->rowCount() ) count = m1->rowCount( m1->index(parent.row(),0) ); //                 //            else if ( parent.row() > (m1->rowCount()-1) && parent.row() < (m1->rowCount() + m2->rowCount()) ) count = m2->rowCount(m2->index(parent.row()-m1->rowCount(), 0)); } return count; } int ModelJoinerProxy::columnCount(const QModelIndex &parent) const { return 1; } 


Now the most interesting thing is that we need to convert the indexes of our models so that they can
interact with each other.

 //         QModelIndex ModelJoinerProxy::mapToSource(const QModelIndex & proxy) const { //       if ( proxy.row() < m1->rowCount() && !proxy.parent().isValid()) { return m1->index(proxy.row(),0) ; } //       if ( proxy.parent().isValid()) { return m1->index(proxy.row(),0, m1->index( proxy.parent().row(),0) ); } //     if ( proxy.row() > (m1->rowCount()-1) && proxy.row() < (m1->rowCount() + m2->rowCount()) ) { int offset = (proxy.row() - m1->rowCount()); return m2->index(offset, 0); } return QModelIndex(); } //        QModelIndex ModelJoinerProxy::mapFromSource(const QModelIndex &source) const { QModelIndex proxy; if (source.model() == m1) { //    if (!source.parent().isValid()) { proxy = index(source.row(), 0); } //    else { QModelIndex source_parent = index(source.parent().row() ,0); proxy = index(source.row(), 0, source_parent); } } //  if (source.model() == m2) { int offset = m1->rowCount() + source.row(); proxy = index(offset, 0); } return proxy; } 


Now it only remains to redefine data (...) so that our model can give the data to the views (and to all whom we want).

 QVariant ModelJoinerProxy::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); return mapToSource(index).data(role); } 


It is also necessary to connect all the necessary signals of source models to the corresponding slots of our model. This is necessary in order for our model to react to any changes in source models.
For example:

 void ModelJoinerProxy::setSourceModel1(QAbstractItemModel *sourceModel1) { m1 = sourceModel1; connect(m1, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(source_dataChanged(QModelIndex,QModelIndex))); ........ void ModelJoinerProxy::setSourceModel2(QAbstractItemModel *sourceModel2) { m2 = sourceModel2; connect(m2, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(source_dataChanged(QModelIndex,QModelIndex))); ........ 


and implement the slots themselves

 void ModelJoinerProxy::source_dataChanged(QModelIndex tl, QModelIndex br) { QModelIndex p_tl = mapFromSource(tl); QModelIndex p_br = mapFromSource(br); emit dataChanged(p_tl, p_br); } 


Well, that's all, our mediator model is ready, it remains only to connect the original models to it, and the mediator model itself to connect to the view. You can optionally make it editable by overriding setData (...)

Conclusion


And why is all this really necessary? I can not find the right words, but I think that people who
The topic touched upon by me is really close to everyone. For example, in my current project, a hierarchy of about 15 proxy models (self-written + standard) and only one source data model are added. Without proxy models, this would take a lot more code, and as a result more bugs, problems with model synchronization, and so on.

I hope that after reading this article, you will discover a new look at MVC in Qt, and you can do it yourself
convert your data structures to fit your GUI needs.
In general, it would be nice to have on hand an addition to MVC Qt consisting of a couple dozen of similar models of intermediaries. For example, if you wanted to group your data by any parameters, use the mediator model for grouping, and so on.

Many thanks to yshurik for patience and irreplaceable advice.

Source: https://habr.com/ru/post/126995/


All Articles