Всем привет!
В этой небольшой статье я научу вас, одному интересному трюку с моделями, который можно реализовать с помощью MVC фреймворка Qt.
Двухуровневая модель дерева:
Модель списка:
В результате трюка мы получим модель объединяющую две вышеприведенные модели:
И так как же это сделать? Я думаю вы уже догадались что сделать это можно прибегнув к помощи QAbstractProxyModel. А вот и нет! К сожалению стандартный класс QAbstractProxyModel может преобразовать лишь одну исходную модель (что тоже неплохо). Поэтому мы напишем свою ModelJoinerProxy, которая будет компоновать наши две исходные модели в одно целое.
И так приступим:
Наша модель посредник представляет собой модель двухуровневого дерева, чтобы достичь этого мы
переопределяем index(..) и parent(..) так как будто мы строим модель обычного дерева.
Дальше нам нужно чтобы наша модель имела правильное количество строк и столбцов,
(для простоты количество столбцов в исходных моделях и модели посреднике будет = 1)
для этого мы переопределяем rowCount(....) и columnCount(.....).
Теперь самое интересное, нам нужно преобразовать индексы наших моделей чтобы они могли
взаимодействовать друг с другом.
Теперь осталось только переопределить data(...) чтобы наша модель могла отдавать данные представлениям (и всем кому мы захотим).
Так же необходимо подключить все необходимые сигналы моделей источников к соответствующим слотам нашей модели. Это нужно для того чтобы наша модель реагировала на любые изменения в моделях источниках.
Например:
и реализовать сами слоты
Ну вот и все, наша модель посредник готова, осталось только подключить к ней исходные модели, а саму модель посредник подключить к представлению. Можно по желанию сделать ее редактируемой переопределив setData(...)
А зачем это все собственно говоря нужно? Не могу подобрать нужных слов, но думаю что люди, которым
действительно близка затронутая мной тема сами все поймут. Например в моем текущем проекте складывается иерархия из примерно 15 прокси моделей (самописные + стандартные) и всего лишь одной исходной модели данных. Без прокси моделей это бы занимало намного больше кода, и как следствие больше багов, проблемы синхронизации моделей ну итд.
Надеюсь что прочитав эту статью, вы откроете для себя новый взгляд на MVC в Qt, и сможете, сами делать
преобразования ваших структур данных в соответствии с потребностями вашего GUI.
А вообще неплохо было бы иметь под рукой дополнение к MVC Qt состоящее из пары десятков подобных моделей посредников. Например захотели вы сгруппировать свои данные по каким-либо параметрам, воспользовались моделью посредником для группировки ну и т.д.
Большое спасибо yshurik за терпение и незаменимые советы.
В этой небольшой статье я научу вас, одному интересному трюку с моделями, который можно реализовать с помощью MVC фреймворка Qt.
Исходные данные для трюка.
Двухуровневая модель дерева:
|Parent 1 -----Child 1 -----Child N |Parent N -----Child 1 -----Child N
Модель списка:
Item1 Item2 Item3
В результате трюка мы получим модель объединяющую две вышеприведенные модели:
|Parent 1 ------Child 1 ------Child N |Parent N ------Child 1 ------Child N |Item1 |Item2 |Item3
Приступим к реализации.
И так как же это сделать? Я думаю вы уже догадались что сделать это можно прибегнув к помощи QAbstractProxyModel. А вот и нет! К сожалению стандартный класс QAbstractProxyModel может преобразовать лишь одну исходную модель (что тоже неплохо). Поэтому мы напишем свою ModelJoinerProxy, которая будет компоновать наши две исходные модели в одно целое.
И так приступим:
//наследуемся от 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; };
Наша модель посредник представляет собой модель двухуровневого дерева, чтобы достичь этого мы
переопределяем index(..) и parent(..) так как будто мы строим модель обычного дерева.
Дальше нам нужно чтобы наша модель имела правильное количество строк и столбцов,
(для простоты количество столбцов в исходных моделях и модели посреднике будет = 1)
для этого мы переопределяем rowCount(....) и 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; }
Теперь самое интересное, нам нужно преобразовать индексы наших моделей чтобы они могли
взаимодействовать друг с другом.
//вернуть индекс исходной модели который соответствует индексу прокси модели 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; }
Теперь осталось только переопределить data(...) чтобы наша модель могла отдавать данные представлениям (и всем кому мы захотим).
QVariant ModelJoinerProxy::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); return mapToSource(index).data(role); }
Так же необходимо подключить все необходимые сигналы моделей источников к соответствующим слотам нашей модели. Это нужно для того чтобы наша модель реагировала на любые изменения в моделях источниках.
Например:
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))); ........
и реализовать сами слоты
void ModelJoinerProxy::source_dataChanged(QModelIndex tl, QModelIndex br) { QModelIndex p_tl = mapFromSource(tl); QModelIndex p_br = mapFromSource(br); emit dataChanged(p_tl, p_br); }
Ну вот и все, наша модель посредник готова, осталось только подключить к ней исходные модели, а саму модель посредник подключить к представлению. Можно по желанию сделать ее редактируемой переопределив setData(...)
Заключение
А зачем это все собственно говоря нужно? Не могу подобрать нужных слов, но думаю что люди, которым
действительно близка затронутая мной тема сами все поймут. Например в моем текущем проекте складывается иерархия из примерно 15 прокси моделей (самописные + стандартные) и всего лишь одной исходной модели данных. Без прокси моделей это бы занимало намного больше кода, и как следствие больше багов, проблемы синхронизации моделей ну итд.
Надеюсь что прочитав эту статью, вы откроете для себя новый взгляд на MVC в Qt, и сможете, сами делать
преобразования ваших структур данных в соответствии с потребностями вашего GUI.
А вообще неплохо было бы иметь под рукой дополнение к MVC Qt состоящее из пары десятков подобных моделей посредников. Например захотели вы сгруппировать свои данные по каким-либо параметрам, воспользовались моделью посредником для группировки ну и т.д.
Большое спасибо yshurik за терпение и незаменимые советы.
