Trouble setting up QDataWidgetMapper
-
I'm new to Qt and trying to set some widgets to update when an row in a
QTableViewis 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_modelis a customQAbstractTableModelsubclass (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! -
I'm new to Qt and trying to set some widgets to update when an row in a
QTableViewis 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_modelis a customQAbstractTableModelsubclass (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!@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 connectingfile_selectionmodeltofiles_mapperis even being executed? If not line #4 is not going to fire.If that is not it: you are connecting aQItemSelectionModel::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous)which sendsQModelIndexarguments and passing (the first) index toQDataWidgetMapper::setCurrentModelIndex()which accepts anint indexparameter. I think C++ would produce a type compilation error for this where perhaps Python/PySide/PyQt lets it through. This probably meansQDataWidgetMapper::currentIndexChanged()does not get fired?That needs correcting. To start testing you might omit theQItemSelectionModelstuff and verify line #4 works when you do some directQDataWidgetMapper::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
QItemSelectionModelare 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 yourcurrentRowChanged()/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.... -
I'm new to Qt and trying to set some widgets to update when an row in a
QTableViewis 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_modelis a customQAbstractTableModelsubclass (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!@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
QDataWidgetMapperhad a flat model, like a list model or a non-hierarchical table model. But yourQFileSystemModelis 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
indexpassed 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.
-
@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
QDataWidgetMapperhad a flat model, like a list model or a non-hierarchical table model. But yourQFileSystemModelis 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
indexpassed 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.
@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.
FilesModelis 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 = TrueI am also uncertain whether the row numbers in the indexes used/returned in a
QItemSelectionModelare 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.setCurrentModelIndexCalls
setCurrentIndex()internally. This convenience slot can be connected to the signalcurrentRowChanged()orcurrentColumnChanged()of another view’s selection model.The following example illustrates how to update all widgets with new data whenever the selection of a
QTableViewnamed 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.fileTableViewis a plainQTableView.)One of the tutorials also uses it in this way:
https://doc.qt.io/qtforpython-6/tutorials/portingguide/chapter3/chapter3.html#python-versionselection_model = self.bookTable.selectionModel() selection_model.currentRowChanged.connect(mapper.setCurrentModelIndex)