【问题标题】:Internal C++ object (PySide2.QtGui.QContextMenuEvent) already deleted on Qtableview内部 C++ 对象 (PySide2.QtGui.QContextMenuEvent) 已在 Qtableview 上删除
【发布时间】:2020-05-16 11:57:41
【问题描述】:

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

所以:

名为 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 的几个线程,上面写着

如果 QObject 在 Python 中超出范围,它将被删除。您必须注意保持对对象的引用: 将其存储为您保留的对象的属性,例如self.window = QMainWindow()
将父 QObject 传递给对象的构造函数,使其归父对象所有

我不确定如何实现。任何帮助表示赞赏。

【问题讨论】:

    标签: python qtableview pyside2


    【解决方案1】:

    说明:

    在 contextMenuEvent 方法中,您正在创建一个弹出窗口并显示它,但这会消耗很少的时间,因此当用户选择一个选项时,该方法已经完成,因此 Qt 删除了不再需要的“事件”对象到。但是你正试图访问一个被 Qt 删除的项目,导致这些错误。

    解决办法:

    有几种解决方案:

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

      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)
      

    【讨论】:

    • 我尝试了解决方案,但它根本不会触发右键菜单。我尝试通过将sql代码移到基本功能之外进行调试并完全删除按钮,但它仍然没有触发右键菜单。我正在使用 PySide2,Pycharm2020.1,Python 版本 3.7 作为我的解释器设置。
    • @Sam 好的,我还没有测试过,所以我需要时间来做这件事,这就是为什么我需要准确地了解你想要做什么。根据您的代码,有两列:“id”和“url”,我想当您右键单击第二列中的项目并选择“在浏览器中打开链接”选项时,url 必须是在浏览器中打开,我正确吗?如果我是,如果我右键单击第一列中的项目会发生什么?
    • 那是正确的。我仍然需要考虑所有用例,但是当文本不是由 https 或 http 标识的 URL 时,它只会忽略右键单击或什么也不做。我可以写一个 for 循环为if not url.startswith( 'http://' ) and not url.startswith( 'https://' ) :。一如既往,我们非常感谢您的帮助。
    • 这在过滤或排序时工作得相当好。谢谢,我会在 mapToGlobal 上阅读。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-24
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 2013-07-28
    相关资源
    最近更新 更多