Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. pyside6 pass custom ListModel to ListView

pyside6 pass custom ListModel to ListView

Scheduled Pinned Locked Moved Solved QML and Qt Quick
qmlpython3qt6pyside6qt quick
3 Posts 2 Posters 1.5k Views
  • 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 6 May 2023, 07:40 last edited by LS-KS 5 Jun 2023, 07:43
    #1

    For a project i have to create an Application with pyside6 (Qt 6.4) and python 3.11. I've done very little Qt C++ programs with widgets but I'm fully new to the QtQick QML programming.
    I've done some Java/ Kotlin applications so in principle i think i understood MVC patterns.

    I have some Data in a 'Produkte.db' file which i want to show in a list. it contains two columns: firstly an int which is an ID and a name.

    i created a python file which reads in the filecontent, splits the data and passes strings to my ListModel.
    Some of the code comes from https://doc.qt.io/qtforpython-6/overviews/model-view-programming.html .

    produktListModel.py:

    import sys
    import typing
    
    import PySide6
    from PySide6 import QtCore
    from PySide6 import QtGui
    
    
    class ProductListModel (QtCore.QAbstractListModel):
    
        def __init__(self, products, parent = None):
            super().__init__(parent)
            self.products = products
    
        def data(self, index, role):
            """Returns an appropriate value for the requested data.
               If the view requests an invalid index, an invalid variant is returned.
               Any valid index that corresponds to a string in the list causes that
              string to be returned."""
            row = index.row()
            if not index.isValid() or row >= len(self.products):
                return None
            if role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole:
                return None
            return self.products[row]
    
        def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
            """Returns the appropriate header string depending on the orientation of
               the header and the section. If anything other than the display role is
               requested, we return an invalid variant."""
            if role != QtCore.Qt.DisplayRole:
                return None
            if orientation == QtCore.Qt.Horizontal:
                return f"Column {section}"
            return f"Row {section}"
    
        def rowCount(self, parent: typing.Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex] = ...) -> int:
            # return length of productList
            return len(self.products)
    
    class Product:
       def __init__(self, id:int, name:str):
           self.id = id
           self.name = name
    
    
    
    def getProducts():
        FILE= "Ressources/data/Produkte.db"
        productList = []
        try:
            # Open product file and read lines to list.
            # Avoid u\ufeff prefix in data by set encoding to utf8-8-sig (source: stackoverflow)
            with open(FILE, 'r', encoding='utf-8-sig') as file:
                list = file.readlines()
    
            for line in list:
                # Split the line by ';' to get the id and name
                product_data = line.strip().split(';')
                product_id = int(product_data[0])
                product_name = str(product_data[1])
                productList.append(Product(id=product_id, name=product_name))
            # Print the list of Product objects
            for product in productList:
                print(f"{product.id}: {product.name}")
        except FileNotFoundError:
                print("Error: could't find product list file 'Produkte.db'")
        except FileExistsError:
                print("Error: file 'Produkte.db' doesn't exist")
    
        return productList
    
    
    

    in my main.py I pass the Model to my QQmlApplicationEngine:

    import sys
    
    from pathlib import Path
    
    from PySide6.QtGui import QGuiApplication
    
    from PySide6.QtQml import QQmlApplicationEngine
    
    from Ressources.model import produktListModel
    
    
    if __name__ == "__main__":
    
        app = QGuiApplication(sys.argv)
    
        engine = QQmlApplicationEngine()
    
        productListModel = produktListModel.ProductListModel(produktListModel.getProducts())
        engine.rootContext().setContextProperty("productListModel", productListModel)
    
        qml_file = Path(__file__).resolve().parent / "Ressources" / "qml" / "main.qml"
    
        engine.load(qml_file)
    
        if not engine.rootObjects():
            sys.exit(-1)
    
        sys.exit(app.exec())
    

    and here is my qml file:

    import QtQuick.Controls 2.5
    import QtQuick.Controls.Material
    import QtQml.Models 2.15
    
    Rectangle {
            id: window
            radius: 10
            color : "white"
    
            border.color: "#546E7A"
            border.width: 2
    
            property bool expanded : true
            property int ex_height : 125
            width: 400
            height: 140
    
    
    
    
            Image {
                id: arrow
                source: "../assets/angle-small-up.png"
                height: 15
                fillMode: Image.PreserveAspectFit
                anchors.left: parent.left
                anchors.top: parent.top
                anchors.margins: 5
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        arrow.source = window.expanded ? "../assets/angle-small-down.png" : "../assets/angle-small-up.png"
                        window.expanded = window.expanded ? false : true
                        window.height = window.expanded? window.ex_height : 25
                        seperator01.visible = seperator01.visible ? false: true
                        seperator02.visible = seperator02.visible ? false : true
                        productlist.visible =  productlist.visible ? false : true
                    }
                }
            }
    
            Text {
                id: title
                height: 15
                anchors.left: arrow. right
                anchors.top : parent.top
                anchors.right: parent.right
                anchors.margins : 5
                text: "Product List"
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
    
            Rectangle {
                id: seperator01
                visible: true
                width : window.width - 10
                height: 1
                color: "#546E7A"
                anchors.top : arrow.bottom
                anchors.left: window.left
                anchors.margins : 5
    
                Behavior on visible { PropertyAnimation{} }
            }
    
            Rectangle {
                id: seperator02
                visible: true
                width : window.width - 10
                height: 1
                color: "#546E7A"
                anchors.bottom : parent.bottom
                anchors.left: window.left
                anchors.margins : 5
    
                Behavior on visible { PropertyAnimation{ duration: 50; easing.type: Easing.OutCubic} }
            }
    
    
    
            ListView {
                id: productlist
                anchors.top: seperator01.bottom
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: seperator02.top
                model: productListModel
                clip: true
                delegate:         Component{
                    id: namedelegate
                    Text {
                        text: model.name
                    }
                }
            }
    
    }
    
    

    When I execute the app I get an error for each line of my data: Unable to assign [undefined] to QString.
    The last two days I tried to understand the issue and read some of the Qt docs. I also tried to find any answer here in the forum without success.

    In the console i can see that the file is read in correctly. I can also debug may.py and see that the engine.rootContext has the Model with correct data.
    So i guessed the error might be in the .qml file?!? But googling and reading didn't bring me to a solution.

    Can anybody can explain me my mistake?

    J 1 Reply Last reply 7 May 2023, 05:54
    0
    • L LS-KS
      6 May 2023, 07:40

      For a project i have to create an Application with pyside6 (Qt 6.4) and python 3.11. I've done very little Qt C++ programs with widgets but I'm fully new to the QtQick QML programming.
      I've done some Java/ Kotlin applications so in principle i think i understood MVC patterns.

      I have some Data in a 'Produkte.db' file which i want to show in a list. it contains two columns: firstly an int which is an ID and a name.

      i created a python file which reads in the filecontent, splits the data and passes strings to my ListModel.
      Some of the code comes from https://doc.qt.io/qtforpython-6/overviews/model-view-programming.html .

      produktListModel.py:

      import sys
      import typing
      
      import PySide6
      from PySide6 import QtCore
      from PySide6 import QtGui
      
      
      class ProductListModel (QtCore.QAbstractListModel):
      
          def __init__(self, products, parent = None):
              super().__init__(parent)
              self.products = products
      
          def data(self, index, role):
              """Returns an appropriate value for the requested data.
                 If the view requests an invalid index, an invalid variant is returned.
                 Any valid index that corresponds to a string in the list causes that
                string to be returned."""
              row = index.row()
              if not index.isValid() or row >= len(self.products):
                  return None
              if role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole:
                  return None
              return self.products[row]
      
          def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
              """Returns the appropriate header string depending on the orientation of
                 the header and the section. If anything other than the display role is
                 requested, we return an invalid variant."""
              if role != QtCore.Qt.DisplayRole:
                  return None
              if orientation == QtCore.Qt.Horizontal:
                  return f"Column {section}"
              return f"Row {section}"
      
          def rowCount(self, parent: typing.Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex] = ...) -> int:
              # return length of productList
              return len(self.products)
      
      class Product:
         def __init__(self, id:int, name:str):
             self.id = id
             self.name = name
      
      
      
      def getProducts():
          FILE= "Ressources/data/Produkte.db"
          productList = []
          try:
              # Open product file and read lines to list.
              # Avoid u\ufeff prefix in data by set encoding to utf8-8-sig (source: stackoverflow)
              with open(FILE, 'r', encoding='utf-8-sig') as file:
                  list = file.readlines()
      
              for line in list:
                  # Split the line by ';' to get the id and name
                  product_data = line.strip().split(';')
                  product_id = int(product_data[0])
                  product_name = str(product_data[1])
                  productList.append(Product(id=product_id, name=product_name))
              # Print the list of Product objects
              for product in productList:
                  print(f"{product.id}: {product.name}")
          except FileNotFoundError:
                  print("Error: could't find product list file 'Produkte.db'")
          except FileExistsError:
                  print("Error: file 'Produkte.db' doesn't exist")
      
          return productList
      
      
      

      in my main.py I pass the Model to my QQmlApplicationEngine:

      import sys
      
      from pathlib import Path
      
      from PySide6.QtGui import QGuiApplication
      
      from PySide6.QtQml import QQmlApplicationEngine
      
      from Ressources.model import produktListModel
      
      
      if __name__ == "__main__":
      
          app = QGuiApplication(sys.argv)
      
          engine = QQmlApplicationEngine()
      
          productListModel = produktListModel.ProductListModel(produktListModel.getProducts())
          engine.rootContext().setContextProperty("productListModel", productListModel)
      
          qml_file = Path(__file__).resolve().parent / "Ressources" / "qml" / "main.qml"
      
          engine.load(qml_file)
      
          if not engine.rootObjects():
              sys.exit(-1)
      
          sys.exit(app.exec())
      

      and here is my qml file:

      import QtQuick.Controls 2.5
      import QtQuick.Controls.Material
      import QtQml.Models 2.15
      
      Rectangle {
              id: window
              radius: 10
              color : "white"
      
              border.color: "#546E7A"
              border.width: 2
      
              property bool expanded : true
              property int ex_height : 125
              width: 400
              height: 140
      
      
      
      
              Image {
                  id: arrow
                  source: "../assets/angle-small-up.png"
                  height: 15
                  fillMode: Image.PreserveAspectFit
                  anchors.left: parent.left
                  anchors.top: parent.top
                  anchors.margins: 5
                  MouseArea {
                      anchors.fill: parent
                      onClicked: {
                          arrow.source = window.expanded ? "../assets/angle-small-down.png" : "../assets/angle-small-up.png"
                          window.expanded = window.expanded ? false : true
                          window.height = window.expanded? window.ex_height : 25
                          seperator01.visible = seperator01.visible ? false: true
                          seperator02.visible = seperator02.visible ? false : true
                          productlist.visible =  productlist.visible ? false : true
                      }
                  }
              }
      
              Text {
                  id: title
                  height: 15
                  anchors.left: arrow. right
                  anchors.top : parent.top
                  anchors.right: parent.right
                  anchors.margins : 5
                  text: "Product List"
                  horizontalAlignment: Text.AlignHCenter
                  verticalAlignment: Text.AlignVCenter
              }
      
              Rectangle {
                  id: seperator01
                  visible: true
                  width : window.width - 10
                  height: 1
                  color: "#546E7A"
                  anchors.top : arrow.bottom
                  anchors.left: window.left
                  anchors.margins : 5
      
                  Behavior on visible { PropertyAnimation{} }
              }
      
              Rectangle {
                  id: seperator02
                  visible: true
                  width : window.width - 10
                  height: 1
                  color: "#546E7A"
                  anchors.bottom : parent.bottom
                  anchors.left: window.left
                  anchors.margins : 5
      
                  Behavior on visible { PropertyAnimation{ duration: 50; easing.type: Easing.OutCubic} }
              }
      
      
      
              ListView {
                  id: productlist
                  anchors.top: seperator01.bottom
                  anchors.left: parent.left
                  anchors.right: parent.right
                  anchors.bottom: seperator02.top
                  model: productListModel
                  clip: true
                  delegate:         Component{
                      id: namedelegate
                      Text {
                          text: model.name
                      }
                  }
              }
      
      }
      
      

      When I execute the app I get an error for each line of my data: Unable to assign [undefined] to QString.
      The last two days I tried to understand the issue and read some of the Qt docs. I also tried to find any answer here in the forum without success.

      In the console i can see that the file is read in correctly. I can also debug may.py and see that the engine.rootContext has the Model with correct data.
      So i guessed the error might be in the .qml file?!? But googling and reading didn't bring me to a solution.

      Can anybody can explain me my mistake?

      J Offline
      J Offline
      jeremy_k
      wrote on 7 May 2023, 05:54 last edited by
      #2

      This is a wild guess, because there is a lot of code for a forum question, but I think the problem is
      text: model.name. With Qt 5 and PyQt or PySide, QML doesn't know anything about python (or C++) symbols. I'm presuming this continues to be true with the Qt 6 equivalents. As such, <python object>.<member> isn't usable.

      In Models and Views in Qt Quick, the Object Instances as Models and C++ Data Models sections offer a few choices. With an otherwise functional model, implementing QAbstractItemModel::roleNames() is probably the easiest route. data() will need to be updated to use the corresponding role numbers.

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

      L 1 Reply Last reply 7 May 2023, 09:54
      0
      • J jeremy_k
        7 May 2023, 05:54

        This is a wild guess, because there is a lot of code for a forum question, but I think the problem is
        text: model.name. With Qt 5 and PyQt or PySide, QML doesn't know anything about python (or C++) symbols. I'm presuming this continues to be true with the Qt 6 equivalents. As such, <python object>.<member> isn't usable.

        In Models and Views in Qt Quick, the Object Instances as Models and C++ Data Models sections offer a few choices. With an otherwise functional model, implementing QAbstractItemModel::roleNames() is probably the easiest route. data() will need to be updated to use the corresponding role numbers.

        L Offline
        L Offline
        LS-KS
        wrote on 7 May 2023, 09:54 last edited by
        #3

        @jeremy_k

        Thank You for the explanation!

        As you said I had to implement the roleNames() Function and adjusted the data().

        The code below now works

        import sys
        import typing
        
        import PySide6
        from PySide6 import QtCore
        from PySide6 import QtGui
        from PySide6.QtCore import QByteArray
        
        
        class ProductListModel (QtCore.QAbstractListModel):
        
            def __init__(self, products, parent = None):
                super().__init__(parent)
                self.products = products
        
            def data(self, index, role):
                """Returns an appropriate value for the requested data.
                   If the view requests an invalid index, an invalid variant is returned.
                   Any valid index that corresponds to a string in the list causes that
                  string to be returned."""
        
                row = index.row()
                if not index.isValid() or row >= len(self.products):
                    return None
                product = self.products[row]
                if role == QtCore.Qt.DisplayRole:
                    return product.name
                elif role == QtCore.Qt.EditRole:
                    return product.name
                elif role == QtCore.Qt.UserRole + 1:
                    return product.id
                elif role == QtCore.Qt.UserRole + 2:
                    return product.name
                return None
        
            def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
                """Returns the appropriate header string depending on the orientation of
                   the header and the section. If anything other than the display role is
                   requested, we return an invalid variant."""
                if role != QtCore.Qt.DisplayRole:
                    return None
                if orientation == QtCore.Qt.Horizontal:
                    return f"Column {section}"
                return f"Row {section}"
        
            def rowCount(self, parent: typing.Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex] = ...) -> int:
                # return length of productList
                return len(self.products)
        
            def roleNames(self):
                    roles = {
                        QtCore.Qt.UserRole + 1: b'id',
                        QtCore.Qt.UserRole + 2: b'name',
                    }
                    return roles
        
        
        class Product:
           def __init__(self, id:int, name:str):
               self.id = id
               self.name = name```
        1 Reply Last reply
        0
        • L LS-KS has marked this topic as solved on 9 May 2023, 12:51

        3/3

        7 May 2023, 09:54

        • Login

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