Image viewer using Qt Model View architecture

Heads up! You've already completed this tutorial.

fjp | 2020-12-07 09:14:44 UTC | #1

Hi, I followed this https://www.pythonguis.com/tutorials/modelview-architecture/ 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

python
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):

python
class ImageView(QtWidgets.QLabel, QtWidgets.QAbstractItemView):
    def __init__(self, parent) -> None:
        print(parent)
        QtWidgets.QLabel.__init__(self, parent)
        QtWidgets.QAbstractItemView.__init__(self, parent)
        #super().__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

enter image description here

Over 10,000 developers have bought Create GUI Applications with Python & Qt!
Create GUI Applications with Python & Qt5
Take a look

Downloadable ebook (PDF, ePub) & Complete Source code

Also available from Leanpub and Amazon Paperback

[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

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:

python
<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:

python
[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:

python
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

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Image viewer using Qt Model View architecture was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt.