GridLayout dynamically populated by model-driven repeater
-
I want to display some Items which are generated from a model, laid out like a grid. GridView is almost exactly what I want except it does not have row/column spanning. GridLayout seems to be the only thing which will lay things out as I want. So I use a Repeater to generate the items from a custom model. Up to this stage things work roughly as I expect. The final problem is I want to dynamically add/remove items via the model. At this stage I get unexpected behaviour.
I have extended the abstractitemmodel example to demonstrate this behaviour. I can upload the whole project if that is helpful, but I think someone might be able to tell me what I'm doing wrong before that.
view.qml
import QtQuick 2.0 import QtQuick.Layouts 1.1 Item { anchors.fill: parent GridLayout { anchors.fill: parent columns: 2 Repeater { model: myModel delegate: Item { Layout.row: animalRow Layout.column: animalColumn Layout.rowSpan: 1 Layout.columnSpan: 1 Layout.minimumWidth: 50 Layout.minimumHeight: 50 Layout.fillWidth: true Layout.fillHeight: true Rectangle { anchors.fill: parent color: "yellow" Text { text: type } } Component.onCompleted: { print(type, animalRow, animalColumn) } } } MouseArea { anchors.fill: parent onClicked: { print("Adding dog"); myModel.addNewAnimal("Dog"); } } } }
model.cpp
Animal::Animal(const QString &type, const QString &size, const int &row, const int &column) : m_type(type), m_size(size), m_row(row), m_column(column) { } QString Animal::type() const { return m_type; } QString Animal::size() const { return m_size; } int Animal::row() const { return m_row; } int Animal::column() const { return m_column; } AnimalModel::AnimalModel(QObject *parent) : QAbstractListModel(parent) { } void AnimalModel::addAnimal(const Animal &animal) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_animals << animal; endInsertRows(); } int AnimalModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); return m_animals.count(); } QVariant AnimalModel::data(const QModelIndex & index, int role) const { if (index.row() < 0 || index.row() >= m_animals.count()) return QVariant(); const Animal &animal = m_animals[index.row()]; if (role == TypeRole) return animal.type(); else if (role == SizeRole) return animal.size(); else if (role == RowRole) return animal.row(); else if (role == ColumnRole) return animal.column(); return QVariant(); } void AnimalModel::addNewAnimal(const QString &view_name) { beginRemoveRows(QModelIndex(), 2, 2); m_animals.removeAt(2); endRemoveRows(); beginInsertRows(QModelIndex(), 2, 2); m_animals.insert(2, Animal(view_name, "Medium", 1, 0)); endInsertRows(); } //![0] QHash<int, QByteArray> AnimalModel::roleNames() const { QHash<int, QByteArray> roles; roles[TypeRole] = "type"; roles[SizeRole] = "size"; roles[RowRole] = "animalRow"; roles[ColumnRole] = "animalColumn"; return roles; }
model.h
#include <QAbstractListModel> #include <QStringList> //![0] class Animal { public: Animal(const QString &type, const QString &size, const int &row, const int &column); //![0] QString type() const; QString size() const; int row() const; int column() const; private: QString m_type; QString m_size; int m_row; int m_column; //![1] }; class AnimalModel : public QAbstractListModel { Q_OBJECT public: enum AnimalRoles { TypeRole = Qt::UserRole + 1, SizeRole, RowRole, ColumnRole }; AnimalModel(QObject *parent = 0); //![1] void addAnimal(const Animal &animal); int rowCount(const QModelIndex & parent = QModelIndex()) const; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; public slots: void addNewAnimal(const QString &view_name); protected: QHash<int, QByteArray> roleNames() const; private: QList<Animal> m_animals; };
Looks like I can't upload screenshots here but I can do that if it will help.
When the example first loads it is fine, and resizing the window works as well. After clicking anywhere an item will be removed from the model and a new one will be added in its place. However, this breaks the layout. Instead of having a grid of squares, you can only see the first column, as it is now as wide as the window. If you resize the window you will see that the other column is still there. The left column grows at at a slower rate than the window when resizing, so it is possible to see a lot of the second column but it never becomes equal in width.
Why is this not behaving as I expect? I think it may be due to the GridLayout not correctly noticing that something has been removed/added. What can I do to fix this? Here is a short list of requirements I have:
- A grid layout (not necessarily a GridLayout, but it seems to be the best for me)
- Row and column spanning
- Items generated from custom model
- Dynamic add/remove from model and therefore layout
- Items must automatically fill their cell in the layout in both dimensions (that's why Layout.fillWidth, Layout.fillHeight = true)
Also one last point, things behave differently when no minimum width/height is set. I'd prefer not to have to do this.
Thanks a lot for the help!