Unable to assign CustomModel to QQmlListModel

511 Views Asked by At

Suppose we have a list model:

from PySide2.QtCore import Qt, QAbstractListModel, QModelIndex
import numpy as np

class AxisModel(QAbstractListModel):
    MaxRole = Qt.UserRole + 1015
    MinRole = Qt.UserRole + 1016
    TicksRole = Qt.UserRole + 1017

    def __init__(self, min_, max_, price_unit, parent=None):
        super(AxisModel, self).__init__(parent)
        self.max = float(max_)
        self.min = float(min_)
        self.price_unit = float(price_unit)
        self.ticks = np.arange(self.min, self.max + self.price_unit, self.price_unit).tolist()

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return len(self.ticks)

    def data(self, index: QModelIndex, role):
        if 0 > index.row() > self.rowCount() and not index.isValid():
            return None
        if role == AxisModel.MinRole:
            return self.min
        if role == AxisModel.MaxRole:
            return self.max
        if role == AxisModel.TicksRole:
            return self.ticks[index.row()]

    def roleNames(self):
        roles = dict()
        roles[AxisModel.MinRole] = b"min"
        roles[AxisModel.MaxRole] = b"max"
        roles[AxisModel.TicksRole] = b"ticks"
        return roles

This is later used in python code:

(...)
    axismodel = AxisModel(min_=min_, max_=max_, price_unit=0.25)

    app = QGuiApplication(sys.argv)
    view = QQuickView()
    view.setResizeMode(QQuickView.SizeRootObjectToView)
    # register models
    view.rootContext().setContextProperty("axis_model", axismodel)
    view.rootContext().setContextProperty("ts_model", tsmodel) # some other model

    qml_file = os.path.join("/home/user/code/project", "simple_test.qml")
    view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))
    if view.status() == QQuickView.Error:
        sys.exit(-1)
    view.show()

    app.exec_()

The QML file that uses it (simplified):

Item {
    Axis {
        model: axis_model
    }
}

The Axis item:

Item {
    id: axis
    property ListModel model
    property real max: model.max
    property real min: model.min
(...)
}

This produces an error: Unable to assign AxisModel to QQmlListModel

Ok, AxisModel is technically not QQmlListModel, but they both inherit from QAbstractItemModel. I do not know how to resolve this. My two thoughts are:

  1. Use a different type than ListModel in Axis.qml definition. I do not know which one. I tried QObject, QVariant, which turn out not to be QML types. Item does not work as AxisModel is not a QQuickItem instance
  2. Annotate AxisModel class in python code with some PySide2 annotation that would inform QML engine about the type. I have no idea how to approach this.

Thanks for your time.

2

There are 2 best solutions below

4
On BEST ANSWER

I think you've correctly identified the problem. Axis model is not a QQmlListModel, so it can't be assigned.

But you should be able to simply define your model like this:

property var model
0
On

To add to the accepted answer, here are changes that were needed to get this to work.

Axis item - using var to let QML decide the type on its own:

Item {
    id: axis
    property var model
    property var min
    property var max
(...)
}

Invokation of the axis item in QML:

Item {

    AxisTest {
        model: axis_model
        min: axis_model.min
        max: axis_model.max
    }
(...)
}

Then, in python:

  1. Remove min/max roles

  2. define getters, setters, and change signal for each property (I still don't know what the signal is supposed to do, so it's a pass for now)

  3. register a property on the class (not the instance), where getter, setter, and notification signal are bound

  4. set initial values of each property in the constructor

class AxisModel(QAbstractListModel):
    def get_min(self):
        return self._min

    def set_min(self, _min):
        self._min = _min

    def get_max(self):
        return self._max

    def set_max(self, _max):
        self._max = _max

    @Signal
    def max_changed(self, _max):
        pass

    @Signal
    def min_changed(self, _min):
        pass

    TicksRole = Qt.UserRole + 1015
    max = Property(float, get_max, set_max, notify=max_changed)
    min = Property(float, get_min, set_min, notify=min_changed)

    def __init__(self, min_, max_, price_unit, parent=None):
        super(AxisModel, self).__init__(parent)
        self._max = float(max_)
        self._min = float(min_)
        self.ticks = np.arange(float(min_), float(max_) + float(price_unit), float(price_unit)).tolist()

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return len(self.ticks)

    def data(self, index: QModelIndex, role):
        if 0 > index.row() > self.rowCount() and not index.isValid():
            return None
        if role == AxisModel.TicksRole:
            return self.ticks[index.row()]
        else:
            return None

    def roleNames(self):
        roles = dict()
        roles[AxisModel.TicksRole] = b"ticks"
        return roles