Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. QAbstractListModel as member of QmlSingleton in Python not working
Qt 6.11 is out! See what's new in the release blog

QAbstractListModel as member of QmlSingleton in Python not working

Scheduled Pinned Locked Moved Solved Qt for Python
5 Posts 2 Posters 917 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • L Offline
    L Offline
    LS-KS
    wrote on last edited by LS-KS
    #1

    I ran into an issue and it seems like I understood something wrong.

    I want to create a project similar to QML Oscilloscope example:
    I have a generator class that has a list of signal-elements, each represents a sine-signal. Controlled by a timer the generator class loops over all signals and evaluates the equations, create a QPointF which is then added to a QLineSeries and rendered live in a ChartView. Although there are some issues with the performance it works. I'll have some ideas to address this later.

    My problem is: I want to render all SignalElements in a ListView so the user may add, remove and edit entries. Because the generator has to interfere with the model I thought it would be good to keep the instance of the QAbstractListModel as a member of my controller instance. like:

    QML_IMPORT_NAME = "io.qt.signalcontroller"
    QML_IMPORT_MAJOR_VERSION = 1
    QML_IMPORT_MINOR_VERSION = 0
    
    
    @QmlElement
    @QmlSingleton
    class SignalController(QObject):
        newValue = Signal(float, float)  # Time, Value
    
        def __init__(self):
            super().__init__(None)
            self.generator = SignalGenerator(self)
            self.signalModel = SignalModel()
            self.series: list[QPointF] = []
    

    and us it inside my QML-file:

    ListView{
                        id: listView
                        anchors.fill: parent
                        anchors.margins: 5
                        implicitWidth: 200
                        clip:true
                        model: SignalController.signalModel
                        delegate:
                        Row {
                            spacing: 5
                            Layout.maximumWidth: listRect.width
                            Label {
                                text: "Amplitude"
                                Layout.maximumWidth: 100
                            }
                           TextField {
                                text: model.amp
                                Layout.maximumWidth: 60
                            }
                          .
                          .
                          .
    

    This way I could later replace the generator by a class that reads data from COM-ports.
    But the app starts with an empty list view. And no error is thrown.
    a print-statement in the model's data method shows it is never called.
    I would like to understand how this should be done correctly.

    I code for 3 years now but since I'm self taught it is fairly possible I missed something.

    Another idea to approach this would be to make the SignalModel also as a Singleton and overloading the __ new __ method so it always returns the existing instance. But unfortunately it always returned a NoneType -object.

    Sure... I could make the model a QmlElement and pass everything from controller to model by linking signals inside a .qml file. But this feels odd.

    Any hints?

    Here is my implementation of my QAbstractListModel:

    class SignalModel(QAbstractListModel):
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self._data: list[SignalElement] = []
    
        def rowCount(self, parent=QModelIndex()):
            return len(self._data)
    
        def roleNames(self):
            return {
                Qt.UserRole + 0 : b"amp",
                Qt.UserRole + 1 : b"off",
                Qt.UserRole + 2 : b"freq",
                Qt.UserRole + 3 : b"phase",
            }
    
        def data(self, index, role=Qt.DisplayRole):
            print(f"Data called: {index.row()} {role}")
            if not index.isValid() or not 0 <= index.row() < len(self._data):
                return None
            if role == Qt.UserRole + 0:
                return self._data[index.row()].amplitude
            elif role == Qt.UserRole + 1:
                return self._data[index.row()].offset
            elif role == Qt.UserRole + 2:
                return self._data[index.row()].frequency
            elif role == Qt.UserRole + 3:
                return self._data[index.row()].phase
            return None
    
    jeremy_kJ 1 Reply Last reply
    0
    • L LS-KS

      I ran into an issue and it seems like I understood something wrong.

      I want to create a project similar to QML Oscilloscope example:
      I have a generator class that has a list of signal-elements, each represents a sine-signal. Controlled by a timer the generator class loops over all signals and evaluates the equations, create a QPointF which is then added to a QLineSeries and rendered live in a ChartView. Although there are some issues with the performance it works. I'll have some ideas to address this later.

      My problem is: I want to render all SignalElements in a ListView so the user may add, remove and edit entries. Because the generator has to interfere with the model I thought it would be good to keep the instance of the QAbstractListModel as a member of my controller instance. like:

      QML_IMPORT_NAME = "io.qt.signalcontroller"
      QML_IMPORT_MAJOR_VERSION = 1
      QML_IMPORT_MINOR_VERSION = 0
      
      
      @QmlElement
      @QmlSingleton
      class SignalController(QObject):
          newValue = Signal(float, float)  # Time, Value
      
          def __init__(self):
              super().__init__(None)
              self.generator = SignalGenerator(self)
              self.signalModel = SignalModel()
              self.series: list[QPointF] = []
      

      and us it inside my QML-file:

      ListView{
                          id: listView
                          anchors.fill: parent
                          anchors.margins: 5
                          implicitWidth: 200
                          clip:true
                          model: SignalController.signalModel
                          delegate:
                          Row {
                              spacing: 5
                              Layout.maximumWidth: listRect.width
                              Label {
                                  text: "Amplitude"
                                  Layout.maximumWidth: 100
                              }
                             TextField {
                                  text: model.amp
                                  Layout.maximumWidth: 60
                              }
                            .
                            .
                            .
      

      This way I could later replace the generator by a class that reads data from COM-ports.
      But the app starts with an empty list view. And no error is thrown.
      a print-statement in the model's data method shows it is never called.
      I would like to understand how this should be done correctly.

      I code for 3 years now but since I'm self taught it is fairly possible I missed something.

      Another idea to approach this would be to make the SignalModel also as a Singleton and overloading the __ new __ method so it always returns the existing instance. But unfortunately it always returned a NoneType -object.

      Sure... I could make the model a QmlElement and pass everything from controller to model by linking signals inside a .qml file. But this feels odd.

      Any hints?

      Here is my implementation of my QAbstractListModel:

      class SignalModel(QAbstractListModel):
      
          def __init__(self, parent=None):
              super().__init__(parent)
              self._data: list[SignalElement] = []
      
          def rowCount(self, parent=QModelIndex()):
              return len(self._data)
      
          def roleNames(self):
              return {
                  Qt.UserRole + 0 : b"amp",
                  Qt.UserRole + 1 : b"off",
                  Qt.UserRole + 2 : b"freq",
                  Qt.UserRole + 3 : b"phase",
              }
      
          def data(self, index, role=Qt.DisplayRole):
              print(f"Data called: {index.row()} {role}")
              if not index.isValid() or not 0 <= index.row() < len(self._data):
                  return None
              if role == Qt.UserRole + 0:
                  return self._data[index.row()].amplitude
              elif role == Qt.UserRole + 1:
                  return self._data[index.row()].offset
              elif role == Qt.UserRole + 2:
                  return self._data[index.row()].frequency
              elif role == Qt.UserRole + 3:
                  return self._data[index.row()].phase
              return None
      
      jeremy_kJ Offline
      jeremy_kJ Offline
      jeremy_k
      wrote on last edited by
      #2

      @LS-KS said in QAbstractListModel as member of QmlSingleton in Python not working:

      But the app starts with an empty list view. And no error is thrown.

      You might need to turn on the appropriate logging categories.

      a print-statement in the model's data method shows it is never called.

      Are you certain that SignalController.signalModel in the QML is referencing self.signalModel from the Python-defined singleton? In the code posted, I don't see a definition of a Qt property. With PyQt, exposing a Python object member as a property to QML requires a little code:

      class Object(QObect):
          @pyqtProperty(str)
          def myProperty():
              return "some text"
      
      Text {
          text: Object.myProperty
      }
      

      Asking a question about code? http://eel.is/iso-c++/testcase/

      L 1 Reply Last reply
      1
      • jeremy_kJ jeremy_k

        @LS-KS said in QAbstractListModel as member of QmlSingleton in Python not working:

        But the app starts with an empty list view. And no error is thrown.

        You might need to turn on the appropriate logging categories.

        a print-statement in the model's data method shows it is never called.

        Are you certain that SignalController.signalModel in the QML is referencing self.signalModel from the Python-defined singleton? In the code posted, I don't see a definition of a Qt property. With PyQt, exposing a Python object member as a property to QML requires a little code:

        class Object(QObect):
            @pyqtProperty(str)
            def myProperty():
                return "some text"
        
        Text {
            text: Object.myProperty
        }
        
        L Offline
        L Offline
        LS-KS
        wrote on last edited by LS-KS
        #3

        @jeremy_k Thank you for your answer!

        I tried the following after reading this link:

        from PySide6.QtCore import QObject, Property
        
        class SignalController(QObject):
          def __init__(self):
            super()__init__(None)
            self.generator = SignalGenerator(self)
            self.signalModel = SignalModel()
        
        
          @Property(SignalModel)
          def signalModel(self):
            return self._signalModel
        
          @signalModel.setter
          def signalModel(self, model: SignalModel):
            self._signalModel = model
        
        

        This throws an exception:

        QQmlExpression: Expression file://...../main.qml:46:21 depends on non-NOTIFYable properties:
            SignalController::signalModel
        

        Don't know what to do with this exception. Couldn't find anything that made sense to me.

        Edit:

        I changed the code to:

        from PySide6.QtCore import QObject, Property
        
        class SignalController(QObject):
        
         signalModelChanged = Signal()
         
         def __init__(self):
            super()__init__(None)
            self.generator = SignalGenerator(self)
            self.signalModel = SignalModel()
        
        
          def get_signalModel(self):
            return self._signalModel
        
          def set_signalModel(self, model: SignalModel):
            self._signalModel = model
        
          signalModel = Property(fget=get_signalModel, fset=set_signalModel, notify=signalModelChanged())
        
        

        No QML-Exception. But still no list entry.

        1 Reply Last reply
        0
        • L Offline
          L Offline
          LS-KS
          wrote on last edited by
          #4

          The error was somewhere else. Both solutions of my last post are working. Closed

          1 Reply Last reply
          0
          • L LS-KS has marked this topic as solved on
          • jeremy_kJ Offline
            jeremy_kJ Offline
            jeremy_k
            wrote on last edited by
            #5

            Congratulations. Can you share the root problem and solution, to help future readers?

            Asking a question about code? http://eel.is/iso-c++/testcase/

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved