Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Trouble setting up QDataWidgetMapper
Qt 6.11 is out! See what's new in the release blog

Trouble setting up QDataWidgetMapper

Scheduled Pinned Locked Moved Unsolved Qt for Python
4 Posts 2 Posters 71 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.
  • A Online
    A Online
    aarqon
    wrote last edited by
    #1

    I'm new to Qt and trying to set some widgets to update when an row in a QTableView is selected. For some reason the selection is not synchronizing between the view's selection model and the data mapper?

    files_mapper = QDataWidgetMapper(window)
    files_mapper.setModel(files_model)
    files_mapper.addMapping(window.ui.dateEdit, FilesModel.Column.DATE)
    files_mapper.currentIndexChanged.connect(print)
    file_selectionmodel = window.ui.fileTableView.selectionModel()
    file_selectionmodel.currentRowChanged.connect(print) file_selectionmodel.currentRowChanged.connect(files_mapper.setCurrentModelIndex)
    

    files_model is a custom QAbstractTableModel subclass (FilesModel).
    The signal connection on line 4 never fires, even when the signal on line 6 does fire.
    It's very possible that I'm not understanding how all of these pieces work. I feel like the docs do not do a good job of explaining how components are meant to fit together!

    JonBJ 2 Replies Last reply
    0
    • A aarqon

      I'm new to Qt and trying to set some widgets to update when an row in a QTableView is selected. For some reason the selection is not synchronizing between the view's selection model and the data mapper?

      files_mapper = QDataWidgetMapper(window)
      files_mapper.setModel(files_model)
      files_mapper.addMapping(window.ui.dateEdit, FilesModel.Column.DATE)
      files_mapper.currentIndexChanged.connect(print)
      file_selectionmodel = window.ui.fileTableView.selectionModel()
      file_selectionmodel.currentRowChanged.connect(print) file_selectionmodel.currentRowChanged.connect(files_mapper.setCurrentModelIndex)
      

      files_model is a custom QAbstractTableModel subclass (FilesModel).
      The signal connection on line 4 never fires, even when the signal on line 6 does fire.
      It's very possible that I'm not understanding how all of these pieces work. I feel like the docs do not do a good job of explaining how components are meant to fit together!

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote last edited by JonB
      #2

      @aarqon said in Trouble setting up QDataWidgetMapper:

      The signal connection on line 4 never fires, even when the signal on line 6 does fire.

      Your code is not legal Python and produces an error at runtime on

      file_selectionmodel.currentRowChanged.connect(print) file_selectionmodel.currentRowChanged.connect(files_mapper.setCurrentModelIndex)
      

      You cannot have multiple statements on a single line like this in Python. You would either need a ; (semi-colon) or separate lines, and I don't know which you have. At which point I do not know what your current "even when the signal on line 6 does fire" means exactly? Both of these statements? One of them? Do we know whether the second statement connecting file_selectionmodel to files_mapper is even being executed? If not line #4 is not going to fire.

      If that is not it: you are connecting a QItemSelectionModel::currentRowChanged(const QModelIndex &current, const QModelIndex &previous) which sends QModelIndex arguments and passing (the first) index to QDataWidgetMapper::setCurrentModelIndex() which accepts an int index parameter. I think C++ would produce a type compilation error for this where perhaps Python/PySide/PyQt lets it through. This probably means QDataWidgetMapper::currentIndexChanged() does not get fired?

      That needs correcting. To start testing you might omit the QItemSelectionModel stuff and verify line #4 works when you do some direct QDataWidgetMapper::setCurrentModelIndex(int index) with an explicit integer index. Always break down your code into simplest steps when testing how something is behaving while developing/debugging.

      When you connect() from Python it is your job to look at the parameters sent by the signal against the parameters accepted by the slot. If they do not match correctly for what you want you have to write some code --- either another "intermediate" slot function or a Python lambda with correct parameters which then calls the slot you really wanted. For example, here quite untested by me you might need something like:

      file_selectionmodel.currentRowChanged.connect(lambda current = current : files_mapper.setCurrentModelIndex(current.row()))
      

      I have not tried testing the code or the Python syntax as I don't have your model code to copy. I am also uncertain whether the row numbers in the indexes used/returned in a QItemSelectionModel are suitable/identical to those directly into the model (not selection) which you need to pass to the model/QDataWidgetMapper::setCurrentModelIndex(). Try to get it right yourself, if you are stuck say so and I will try actually testing the required Python code.

      UPDATE
      I think what I have written about your currentRowChanged()/setCurrentModelIndex() parameters is incorrect, I did not look at your code closely enough. I am now trying to get this working correctly in PySide6 and will post when I have it....

      1 Reply Last reply
      0
      • A aarqon

        I'm new to Qt and trying to set some widgets to update when an row in a QTableView is selected. For some reason the selection is not synchronizing between the view's selection model and the data mapper?

        files_mapper = QDataWidgetMapper(window)
        files_mapper.setModel(files_model)
        files_mapper.addMapping(window.ui.dateEdit, FilesModel.Column.DATE)
        files_mapper.currentIndexChanged.connect(print)
        file_selectionmodel = window.ui.fileTableView.selectionModel()
        file_selectionmodel.currentRowChanged.connect(print) file_selectionmodel.currentRowChanged.connect(files_mapper.setCurrentModelIndex)
        

        files_model is a custom QAbstractTableModel subclass (FilesModel).
        The signal connection on line 4 never fires, even when the signal on line 6 does fire.
        It's very possible that I'm not understanding how all of these pieces work. I feel like the docs do not do a good job of explaining how components are meant to fit together!

        JonBJ Offline
        JonBJ Offline
        JonB
        wrote last edited by JonB
        #3

        @aarqon
        I have crossed out stuff in my initial reply where I did not look correctly at your code.

        In a word: your code would work if the QDataWidgetMapper had a flat model, like a list model or a non-hierarchical table model. But your QFileSystemModel is hierarchical (has parents) and so its indexes do not work for QDWM as-is. The key is void QDataWidgetMapper::setRootIndex(const QModelIndex &index)

        Sets the root item to index. This can be used to display a branch of a tree. Pass an invalid model index to display the top-most branch.

        We need to set that to the parent of the selected item to make the index passed in correct. A complete example of this working is:

        import sys
        from PySide6 import QtWidgets
        
        def selection_changed(index, _previous):
            dwp.setRootIndex(index.parent())
            dwp.setCurrentModelIndex(index)
        
        
        if __name__ == '__main__':
            app = QtWidgets.QApplication([])
        
            window = QtWidgets.QWidget()
            window.setGeometry(100, 100, 400, 400)
            layout = QtWidgets.QVBoxLayout()
            window.setLayout(layout)
        
            fs = QtWidgets.QFileSystemModel()
            fs.setRootPath("/home/jon")
        
            tv = QtWidgets.QTreeView(window)
            tv.setModel(fs)
            tv.setRootIndex(fs.index("/home/jon"))
            layout.addWidget(tv)
        
            dwp = QtWidgets.QDataWidgetMapper()
            dwp.setModel(fs)
            leName = QtWidgets.QLineEdit()
            layout.addWidget(leName)
            dwp.addMapping(leName, 0)
        
            fsm = tv.selectionModel()
            fsm.currentChanged.connect(selection_changed)
        
            window.show()
            sys.exit(app.exec())
        

        You may have to a bit careful if you intend to use this for editing selected files/directories (possibly to cope correctly with an unsaved edit in the line edit when you click elsewhere), but I don't even know whether you intend to allow that. For now this at least shows the selected item in the line edit at the bottom of the window.

        A 1 Reply Last reply
        1
        • JonBJ JonB

          @aarqon
          I have crossed out stuff in my initial reply where I did not look correctly at your code.

          In a word: your code would work if the QDataWidgetMapper had a flat model, like a list model or a non-hierarchical table model. But your QFileSystemModel is hierarchical (has parents) and so its indexes do not work for QDWM as-is. The key is void QDataWidgetMapper::setRootIndex(const QModelIndex &index)

          Sets the root item to index. This can be used to display a branch of a tree. Pass an invalid model index to display the top-most branch.

          We need to set that to the parent of the selected item to make the index passed in correct. A complete example of this working is:

          import sys
          from PySide6 import QtWidgets
          
          def selection_changed(index, _previous):
              dwp.setRootIndex(index.parent())
              dwp.setCurrentModelIndex(index)
          
          
          if __name__ == '__main__':
              app = QtWidgets.QApplication([])
          
              window = QtWidgets.QWidget()
              window.setGeometry(100, 100, 400, 400)
              layout = QtWidgets.QVBoxLayout()
              window.setLayout(layout)
          
              fs = QtWidgets.QFileSystemModel()
              fs.setRootPath("/home/jon")
          
              tv = QtWidgets.QTreeView(window)
              tv.setModel(fs)
              tv.setRootIndex(fs.index("/home/jon"))
              layout.addWidget(tv)
          
              dwp = QtWidgets.QDataWidgetMapper()
              dwp.setModel(fs)
              leName = QtWidgets.QLineEdit()
              layout.addWidget(leName)
              dwp.addMapping(leName, 0)
          
              fsm = tv.selectionModel()
              fsm.currentChanged.connect(selection_changed)
          
              window.show()
              sys.exit(app.exec())
          

          You may have to a bit careful if you intend to use this for editing selected files/directories (possibly to cope correctly with an unsaved edit in the line edit when you click elsewhere), but I don't even know whether you intend to allow that. For now this at least shows the selected item in the line edit at the bottom of the window.

          A Online
          A Online
          aarqon
          wrote last edited by
          #4

          @JonB Thanks for taking a look Jon.
          (The line formatting in my code block up there is a mistake from when I pasted the code in! My bad there.)

          But your QFileSystemModel is hierarchical (has parents)

          I'm not using a QFileSystemModel.
          FilesModel is a list of files with metadata:

          class FilesModel(QAbstractTableModel):
              class Column(IntEnum):
                  ID = 0
                  PATH = auto()
                  HASH = auto()
                  TAGS = auto()
                  TYPE = auto()
                  DESCRIPTION = auto()
                  SOURCES = auto()
                  DATE = auto()
                  EXIF = auto()
                  PARENT = auto()
              
              def __init__(self, source: list[Files] = []):
                  super().__init__()
                  self._files = source
                  self._thumbs = {x.hash: None for x in source}
                  for file in source:
                      self._thumbs[file.hash] = QIcon(str(file.path))
              
              def data(self, index, role):
                  match role:
                      case Qt.ItemDataRole.DisplayRole | Qt.ItemDataRole.EditRole:
                          f = self._files[index.row()]
                          match index.column():
                              case self.Column.ID:
                                  return f.id
                              case self.Column.PATH:
                                  return f.path.name
                              case self.Column.HASH:
                                  return f.hash
                              case self.Column.TAGS:
                                  return f.tags
                              case self.Column.TYPE:
                                  return f.type
                              case self.Column.DESCRIPTION:
                                  return f.description
                              case self.Column.SOURCES:
                                  return f.sources
                              case self.Column.DATE:
                                  return f.date
                              case self.Column.EXIF:
                                  return f.includeExif
                              case self.Column.PARENT:
                                  return f.parent
                              case _:
                                  return None
                      case Qt.ItemDataRole.DecorationRole:
                          match index.column():
                              case self.Column.PATH:
                                  return self._thumbs[self._files[index.row()].hash]
                              case _:
                                  return None
              
              def rowCount(self, index):
                  return len(self._files)
          
              def columnCount(self, index):
                  return len(self.Column)
          
              def headerData(self, section, orientation, role):
                  if orientation == Qt.Orientation.Horizontal:
                      if role == Qt.ItemDataRole.DisplayRole:
                          return self.Column(section).name.capitalize()
          
          ##########
          # Referenced in the above:
          ##########
          
          type Tag = str
          type Hash = str     # sha256 hex string
          type Date = str     # expecting ISO 8601 YYYY-MM-DD
          
          class FileType(IntEnum):
              IMAGE = 1
              VIDEO = auto()
              TEXT = auto()
              AUDIO = auto()
          
          @dataclass
          class File:
              type: FileType = None
              path: Path = None
              hash: Hash = None # sha256
              id: int = None
              tags: list[Tag] = field(default_factory=list)
              description: str = None
              sources: list[str] = field(default_factory=list)
              parent: Hash = None
              date: QDate = None
              includeExif: bool = True
          

          I am also uncertain whether the row numbers in the indexes used/returned in a QItemSelectionModel are suitable/identical to those directly into the model (not selection) which you need to pass to the model/QDataWidgetMapper::setCurrentModelIndex().

          I'm a little unsure about that as well however the docs make it very explicit:
          https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QDataWidgetMapper.html#PySide6.QtWidgets.QDataWidgetMapper.setCurrentModelIndex

          Calls setCurrentIndex() internally. This convenience slot can be connected to the signal currentRowChanged() or currentColumnChanged() of another view’s selection model.

          The following example illustrates how to update all widgets with new data whenever the selection of a QTableView named myTableView changes:

          mapper = QDataWidgetMapper()
          connect(myTableView.selectionModel(), QItemSelectionModel.currentRowChanged,
          mapper.setCurrentModelIndex)
          

          The specific connect() call there doesn't seem to be valid (too literally copied from the C++?) but the idea is clear.
          (window.ui.fileTableView is a plain QTableView.)

          One of the tutorials also uses it in this way:
          https://doc.qt.io/qtforpython-6/tutorials/portingguide/chapter3/chapter3.html#python-version

          selection_model = self.bookTable.selectionModel()
          selection_model.currentRowChanged.connect(mapper.setCurrentModelIndex)
          
          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
          • Unsolved