Внутренний объект C++ (PySide2.QtGui.QContextMenuEvent), уже удаленный в Qtableview

У меня есть очень простая программа, которая открывает БД и загружает табличное представление.

So:

Файл макета с именем 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.

Я просмотрел несколько потоков подобных этому здесь, в котором говорится

Если QObject выходит из области видимости в Python, он будет удален. Вы должны позаботиться о сохранении ссылки на объект: сохраните его как атрибут объекта, который вы храните, например. self.window = QMainWindow()
Передать родительский объект QObject в конструктор объекта, чтобы он стал владельцем родителя

Я не уверен, как этого можно достичь. Любая помощь приветствуется.


person Sam    schedule 16.05.2020    source источник


Ответы (1)


Объяснение:

В методе contextMenuEvent вы создаете всплывающее окно и показываете его, но это занимает очень мало времени, поэтому, когда пользователь выбирает параметр, этот метод уже завершен, и поэтому Qt удаляет объект «событие», поскольку он больше не нужен. Но вы пытаетесь получить доступ к элементу, удаленному Qt, что вызывает такие ошибки.

Решение:

Есть несколько решений:

  • Используйте exec_() вместо popup(), чтобы метод contextMenuEvent не заканчивал выполнение и, таким образом, объект «событие» не удалялся, кроме того, 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)
    
  • Получите информацию из текста и передайте ее лямбде перед отображением всплывающего окна, чтобы больше не было необходимости использовать событие:

    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)
    
person eyllanesc    schedule 16.05.2020
comment
Я попробовал решение, но оно просто не вызывает меню правой кнопки мыши. Я попытался выполнить отладку, переместив код sql за пределы основной функции и полностью удалив кнопку, но он по-прежнему не вызывает меню правой кнопки мыши. Я использую PySide2, Pycharm2020.1 с Python версии 3.7 в качестве настройки интерпретатора. - person Sam; 17.05.2020
comment
@Sam Хорошо, я не проверял это, поэтому мне потребуется время, чтобы сделать это, поэтому мне нужно точно понять, что вы хотите сделать. В соответствии с вашим кодом есть 2 столбца: идентификатор и URL-адрес, и я полагаю, что когда вы щелкаете правой кнопкой мыши элемент во втором столбце и выбираете опцию «Открыть ссылку в браузере», тогда URL-адрес должен быть открыт в браузере, am Я прав? Если да, что произойдет, если я щелкну правой кнопкой мыши элемент в первом столбце? - person eyllanesc; 17.05.2020
comment
это было бы правильно. Мне еще нужно подумать обо всех вариантах использования, но когда текст не является URL-адресом, идентифицированным https или http , он просто игнорирует щелчок правой кнопкой мыши или ничего не делает. Я могу написать цикл for как if not url.startswith( 'http://' ) and not url.startswith( 'https://' ) :. Как всегда ваша помощь очень ценится. - person Sam; 17.05.2020
comment
это работает достаточно хорошо, в том числе при фильтрации или сортировке. Спасибо, почитаю на mapToGlobal. - person Sam; 17.05.2020