内部 C++ 对象 (PySide2.QtGui.QContextMenuEvent) 已在 Qtableview 上删除

Internal C++ object (PySide2.QtGui.QContextMenuEvent) already deleted on Qtableview

我有一个非常简单的程序,可以打开一个数据库并加载一个表视图。

所以:

名为 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

我的主文件名为 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_())

一旦您 运行 程序并单击加载,数据库就会加载到视图中。右键单击菜单按预期显示,但在单击时出现错误。

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

我查看了几个线程 like this one here 上面写着

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

我不确定如何实现。感谢任何帮助。

解释:

在 contextMenuEvent 方法中,您正在创建一个弹出窗口并显示它,但是这只消耗很少的时间,所以当用户选择一个选项时,该方法已经完成,因此 Qt 删除了 "event" 对象,因为它不再需要。但是您正在尝试访问被 Qt 删除的项目,导致出现此类错误。

解决方案:

有几种解决方法:

  • 使用exec_()而不是popup(),这样contextMenuEvent方法不会完成执行,因此不会删除"event"对象,另外QTableView会没有 item() 方法,所以它会抛出另一个异常,因此使用索引方法:

    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)
    
  • 从文本中获取信息并在显示弹出窗口之前将其传递给 lambda,因此不再需要使用事件:

    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)