2
votes

I have a very simple program that opens a DB and loads a tableview.

So:

Layout file called TestLayouts.py

# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'basic.ui'
##
## Created by: Qt User Interface Compiler version 5.14.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import (QCoreApplication, QMetaObject,
                            QRect)
from PySide2.QtWidgets import *


class Ui_MainWindow(object) :
    def setupUi(self, MainWindow) :
        if not MainWindow.objectName() :
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(1034, 803)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.pushButton = QPushButton(self.centralwidget)
        self.pushButton.setObjectName(u"pushButton")
        self.pushButton.setGeometry(QRect(920, 730, 89, 25))
        self.tableView = QTableView(self.centralwidget)
        self.tableView.setObjectName(u"tableView")
        self.tableView.setGeometry(QRect(10, 20, 1001, 711))
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QStatusBar(MainWindow)
        self.statusbar.setObjectName(u"statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow) :
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.pushButton.setText(QCoreApplication.translate("MainWindow", u"PushButton", None))
    # retranslateUi

My main file called tests.py

import sys
import webbrowser

from PySide2 import QtWidgets, QtGui
from PySide2.QtCore import QCoreApplication, QSortFilterProxyModel
from PySide2.QtCore import Slot
from PySide2.QtSql import QSqlDatabase, QSqlQueryModel
from PySide2.QtWidgets import QMenu, QAction
from TestLayouts import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow) :

    def __init__(self, parent=None) :
        super(MainWindow, self).__init__()

        self.setupUi(self)
        self.showMaximized()
        self.pushButton.clicked.connect(self._basic)

    def _basic(self) :
        db = QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName("data.sqlite")
        db.open()
        sourceModel = QSqlQueryModel()
        sourceModel.setQuery(
            "SELECT id,url FROM database",
            db)
        proxyModel = QSortFilterProxyModel(self)
        proxyModel.setSourceModel(sourceModel)
        self.tableView.setModel(proxyModel)
        self.tableView.setSortingEnabled(True)


    @Slot()
    def closeEvent(self, event) :
        super(MainWindow, self).closeEvent(event)
        QCoreApplication.instance().quit()

    def contextMenuEvent(self, event) :
        self.menu = QMenu(self)
        openlinkAction = QAction('Open Link In A Browser', self)
        openlinkAction.triggered.connect(lambda : self.openInANewTab(event))
        self.menu.addAction(openlinkAction)
        self.menu.popup(QtGui.QCursor.pos())

    def openInANewTab(self, event) :
        self.row = self.tableView.rowAt(event.pos().y())
        self.col = self.tableView.columnAt(event.pos().x())
        self.cell = self.tableView.item(self.row, self.col)
        cellText = self.cell.text()
        webbrowser.open(cellText)


if __name__ == "__main__" :
    app = QtWidgets.QApplication([])
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

Once you run the program and click load, the db loads into view. The right click menu is displayed as expected but on clicking I get the error.

RuntimeError: Internal C++ object (PySide2.QtGui.QContextMenuEvent) already deleted.

I have looked at the few threads like this one here which says

If a QObject falls out of scope in Python, it will get deleted. You have to take care of keeping a reference to the object: Store it as an attribute of an object you keep around, e.g. self.window = QMainWindow()
Pass a parent QObject to the object’s constructor, so it gets owned by the parent

I am not sure how this can be achieved. Any help is appreciated.

1

1 Answers

2
votes

Explanation:

In the contextMenuEvent method you are creating a popup and you are showing it but that consumes very little time so when the user selects an option that method is already finished and therefore Qt removes the "event" object as it is no longer needs to. But you are trying to access an item removed by Qt causing those kinds of errors.

Solution:

There are several solutions:

  • Use exec_() instead of popup() so that the contextMenuEvent method does not finish executing and thus the "event" object is not removed, in addition the QTableView does not have an item() method so it will throw another exception, instead therefore use the index method:

    def contextMenuEvent(self, event):
        self.menu = QMenu(self)
        openlinkAction = QAction("Open Link In A Browser", self)
        openlinkAction.triggered.connect(lambda: self.openInANewTab(event))
        self.menu.addAction(openlinkAction)
        self.menu.exec_(QtGui.QCursor.pos())
    
    def openInANewTab(self, event):
        gp = self.mapToGlobal(event.pos())
        vp = self.tableView.viewport().mapFromGlobal(gp)
        index = self.tableView.indexAt(vp)
        if index.isValid():
            cellText = index.data()
            if isinstance(cellText, str):
                webbrowser.open(cellText)
    
  • Get the information from the text and pass it to the lambda before displaying the popup so it is no longer necessary to use the event:

    def contextMenuEvent(self, event):
        gp = self.mapToGlobal(event.pos())
        vp = self.tableView.viewport().mapFromGlobal(gp)
        index = self.tableView.indexAt(vp)
        if not index.isValid():
            return
        self.menu = QMenu(self)
        cellText = index.data()
        openlinkAction = QAction("Open Link In A Browser", self)
        openlinkAction.triggered.connect(
            lambda *args, text=cellText: self.openInANewTab(text)
        )
        self.menu.addAction(openlinkAction)
        self.menu.popup(QtGui.QCursor.pos())
    
    def openInANewTab(self, text):
        if isinstance(text, str):
            webbrowser.open(text)