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
Forum Updated to NodeBB v4.3 + New Features

QAbstractListModel as member of QmlSingleton in Python not working

Scheduled Pinned Locked Moved Solved Qt for Python
5 Posts 2 Posters 423 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