Hi, I followed this Using the PyQt5 ModelView Architecture to build a simple Todo app tutorial, and I am trying to display an image depending on the currently selected image path from the QTableView.
I think a possible approach is to create a custom QAbstractItemView to display the currently selected image.
The ImageModel
and main window class (in image_app.py
) look like this
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QFileDialog
from PySide2.QtCore import Qt
from MainWindow import Ui_MainWindow
class ImageModel(QtCore.QAbstractListModel):
def __init__(self, images=None):
super().__init__()
self.images = images or []
def data(self, index, role):
if role == Qt.DisplayRole:
_, text = self.images[index.row()]
return text
def rowCount(self, index):
return len(self.images)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.model = ImageModel()
self.tableView.setModel(self.model)
self.imageView.setModel(self.model)
self.tableView.selectionModel().selectionChanged.connect(self.imageView.selectionChanged)
self.actionImport.triggered.connect(self.onImportImageClicked)
def onImportImageClicked(self, s):
self.open_file()
def open_file(self):
filename, _ = QFileDialog.getOpenFileName(
self,
"Open file",
"",
"Ok Image (*.png *.jpg *.bmp *.jpeg);;" "All files(*.*)",
)
if filename:
self.add(filename)
def add(self, image_filename):
# Access the list via the model.
self.model.images.append((False, image_filename))
# Trigger refresh.
self.model.layoutChanged.emit()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
And my custom QAbstractItemView should display the image using a QLabel (defined in image_view.py
):
class ImageView(QtWidgets.QLabel, QtWidgets.QAbstractItemView):
def __init__(self, parent) -> None:
print(parent)
QtWidgets.QLabel.__init__(self, parent)
QtWidgets.QAbstractItemView.__init__(self, parent)
#super(ImageView, self).__init__()
#self.label = QtWidgets.QLabel()
def selectionChanged(self, selected, deselected):
print("selectionChanged", type(selected))
indexes = selected.indexes()
print(indexes)
if not indexes:
return
image_filename = self.model().data(indexes[0], Qt.DisplayRole)
print(image_filename)
pixmap = QtGui.QPixmap(image_filename).scaled(128, 128, QtCore.Qt.KeepAspectRatio)
self.setPixmap(pixmap)
The GUI is designed with Qt Designer and looks like this
I have promoted a QWidget in the Input tab to the ImageView
class in the image_view.py
It can display images when one is selected from the QTableListView, but after I run the MainWindow I get this console output:
<PySide2.QtWidgets.QWidget(0x7f8cd002cad0, name="tabInput") at 0x7f8ceaebc980>
QObject::connect: No such slot ImageView::_q_modelDestroyed()
QObject::connect: No such slot ImageView::dataChanged(QModelIndex,QModelIndex,QVector<int>)
QObject::connect: No such slot ImageView::_q_headerDataChanged()
QObject::connect: No such slot ImageView::rowsInserted(QModelIndex,int,int)
QObject::connect: No such slot ImageView::_q_rowsInserted(QModelIndex,int,int)
QObject::connect: No such slot ImageView::rowsAboutToBeRemoved(QModelIndex,int,int)
QObject::connect: No such slot ImageView::_q_rowsRemoved(QModelIndex,int,int)
QObject::connect: No such slot ImageView::_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)
QObject::connect: No such slot ImageView::_q_columnsAboutToBeRemoved(QModelIndex,int,int)
QObject::connect: No such slot ImageView::_q_columnsRemoved(QModelIndex,int,int)
QObject::connect: No such slot ImageView::_q_columnsInserted(QModelIndex,int,int)
QObject::connect: No such slot ImageView::_q_columnsMoved(QModelIndex,int,int,QModelIndex,int)
QObject::connect: No such slot ImageView::reset()
QObject::connect: No such slot ImageView::_q_layoutChanged()
QObject::connect: No such slot ImageView::selectionChanged(QItemSelection,QItemSelection)
QObject::connect: No such slot ImageView::currentChanged(QModelIndex,QModelIndex)
selectionChanged <class 'PySide2.QtCore.QItemSelection'>
[]
selectionChanged <class 'PySide2.QtCore.QItemSelection'>
[<PySide2.QtCore.QModelIndex(0,0,0x0,TodoModel(0x1b10c90)) at 0x7f8ceaec24c0>]
/home/fjp/Pictures/motor.png
It seems also that the QLabel is displayed above the pixmap and when I click it, the app crashes:
[1] 150858 segmentation fault (core dumped) python image_app.py
How to avoid these warnings and crashing the app? Whats the correct way to display an image with the currently selected file name in the QTableView? Should I avoid the QAbstractItemView and just use a QLabel to update its pixmap when the selection changes?
Edit
Here is the pyside2 generated gui MainWindow.py
, used in image_app.py
to better reproduce this issue:
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from image_view import ImageView
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(800, 600)
self.actionImport = QAction(MainWindow)
self.actionImport.setObjectName(u"actionImport")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.verticalLayout = QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.splitter = QSplitter(self.centralwidget)
self.splitter.setObjectName(u"splitter")
self.splitter.setOrientation(Qt.Vertical)
self.tabWidget = QTabWidget(self.splitter)
self.tabWidget.setObjectName(u"tabWidget")
self.tabInput = QWidget()
self.tabInput.setObjectName(u"tabInput")
self.verticalLayout_2 = QVBoxLayout(self.tabInput)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.imageView = ImageView(self.tabInput)
self.imageView.setObjectName(u"imageView")
self.verticalLayout_2.addWidget(self.imageView)
self.tabWidget.addTab(self.tabInput, "")
self.splitter.addWidget(self.tabWidget)
self.tableView = QTableView(self.splitter)
self.tableView.setObjectName(u"tableView")
self.splitter.addWidget(self.tableView)
self.verticalLayout.addWidget(self.splitter)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 800, 22))
self.menuFile = QMenu(self.menubar)
self.menuFile.setObjectName(u"menuFile")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName(u"statusbar")
MainWindow.setStatusBar(self.statusbar)
self.menubar.addAction(self.menuFile.menuAction())
self.menuFile.addAction(self.actionImport)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
self.actionImport.setText(QCoreApplication.translate("MainWindow", u"Import", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabInput), QCoreApplication.translate("MainWindow", u"Input", None))
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
# retranslateUi