Search code examples
qtqmlqabstractitemmodel

How to implement a list depending on the elements of a different QAbstractListModel in QML?


In QML, I want to specify a list of options (strings) that the user can choose to insert into my backend. But I don't want the list to contain strings that are already in the backend. And if the backend is updated, then the list of options should also update.

First I wrote a subclass QAbstractListModel which exposes a list of QString interface for my backend. It follows the guidelines from the docs, and exposes two custom functions push_back and indexOf.

class MyModel: public QAbstractListModel {
    Q_OBJECT
public:
    MyModel() { ... } // connect to the backend

    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override {
        Q_UNUSED(role);
        if (!hasIndex(index.row(), index.column())) {
            return {};
        }
        return ... // retreive backend object at index.row() and convert to QString
    }


    Q_INVOKABLE void push_back(const QString& item) {
        beginInsertRows(QModelIndex(), int(_data->size()), int(_data->size()));

        ... // convert QString to backend data type and insert into backend

        endInsertRows();
    }

    Q_INVOKABLE int indexOf(const QString& item) {
        return ... // convert QString to backend type, and search backend for this object. return -1 if not found
    }


private:
    // pointer to backend object
};

I was thinking something like this in my QML. First a view of the strings that are already in the backend. This part works fine.

ListView {
  anchors.fill: parent
  spacing: 5
  model: backend // an instance MyModel interface, passed in from main.cpp

  delegate: Rectangle {
    height: 25
    width: 200
    Text { text: model.display}
  }
}

And then the list of options that can be added to the backend, which are not already part of the backend. This part doesn't work.

ListView {
  anchors.fill: parent
  spacing: 5
  model: ["option1", "option2", "option3"]

  delegate: Rectangle {
    visible: backend.indexOf(model.modelData) >= 0
    height: 25
    width: 200
    MouseArea {
        id: mouseArea1
        anchors.fill: parent
        onClicked: {
            backend.push_back(model.modelData)
        }
    }
    Text { text: model.modelData }
  }
}

The problem is, when strings are added to the backend, this list does not refresh. I don't think Qt understands that backend.indexOf needs to be recomputed whenever its modified.


Solution

  • You are correct about the problem. There's no binding that will re-call indexOf. One way to fix it is you could add a Connections object so you can listen for a specific signal and then manually update the visible property:

      delegate: Rectangle {
        visible: backend.indexOf(model.modelData) >= 0
        height: 25
        width: 200
        MouseArea {
            id: mouseArea1
            anchors.fill: parent
            onClicked: {
                backend.push_back(model.modelData)
            }
        }
        Text { text: model.modelData }
    
        Connections {
          target: backend
          onCountChanged: visible = (backend.indexOf(model.modelData) >= 0)
        }
      }