首页 > 解决方案 > PyQt6删除带有嵌套类的自定义小部件导致程序崩溃

问题描述

我正在使用 Python 3.9.5。

我在我的项目中遇到了一些严重的问题,这里是一个最小可重现的示例代码,以及一些描述。

from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *

class Editor(QTextEdit):
    doubleClicked = pyqtSignal(QTextEdit)
    def __init__(self):
        super().__init__()
        self.setReadOnly(True)
    
    def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
        self.doubleClicked.emit(self)

class textcell(QGroupBox):
    def __init__(self, text):
        super().__init__()
        self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
        self.label = QLabel(text)
        self.apply = makebutton('Apply')
        self.apply.hide()
        self.editor = Editor()
        self.editor.doubleClicked.connect(lambda: self.editor.setReadOnly(False))
        self.editor.doubleClicked.connect(self.apply.show)
        self.hbox = QHBoxLayout()
        self.hbox.addSpacerItem(spacer)
        self.hbox.addWidget(self.apply)
        self.vbox = QVBoxLayout()
        self.vbox.addWidget(self.label)
        self.vbox.addWidget(self.editor)
        self.vbox.addLayout(self.hbox)
        self.setLayout(self.vbox)
        self.apply.clicked.connect(self.on_ApplyClick)
    
    def on_ApplyClick(self):
        self.editor.setReadOnly(True)
        self.apply.hide()


def makebutton(text):
    button = QPushButton()
    button.setFixedSize(60, 20)
    button.setText(text)
    return button


class songpage(QGroupBox):
    def __init__(self, texts):
        super().__init__()
        self.init(texts)
        self.setCheckable(True)
        self.setChecked(False)
    
    def init(self, texts):
        self.vbox = QVBoxLayout()
        artist = textcell('Artist')
        artist.editor.setText(texts[0])
        album = textcell('Album')
        album.editor.setText(texts[1])
        title = textcell('Title')
        title.editor.setText(texts[2])
        self.vbox.addWidget(artist)
        self.vbox.addWidget(album)
        self.vbox.addWidget(title)
        self.setLayout(self.vbox)

spacer = QSpacerItem(0, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)

class Ui_MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(405, 720)
        self.setWindowTitle('example')
        frame = self.frameGeometry()
        center = self.screen().availableGeometry().center()
        frame.moveCenter(center)
        self.move(frame.topLeft())
        self.centralwidget = QWidget(self)
        vbox = QVBoxLayout(self.centralwidget)
        hbox = QHBoxLayout()
        add = makebutton('Add')
        delete = makebutton('Delete')
        hbox.addWidget(add)
        hbox.addSpacerItem(spacer)
        hbox.addWidget(delete)
        vbox.addLayout(hbox)
        self.scrollArea = QScrollArea(self.centralwidget)
        self.scrollArea.setWidgetResizable(True)
        self.scrollAreaWidgetContents = QWidget()
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents)
        self.verticalLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
        vbox.addWidget(self.scrollArea)
        self.setCentralWidget(self.centralwidget)
        add.clicked.connect(self.addObj)
        delete.clicked.connect(self.deleteObj)
    def addObj(self):
        Obj = songpage(('AAA', 'BBB', 'CCC'))
        self.verticalLayout.addWidget(Obj)
    def deleteObj(self):
        item = self.verticalLayout.itemAt(0)
        widget = item.widget()
        self.verticalLayout.removeItem(item)
        self.verticalLayout.removeWidget(widget)


app = QApplication([])
window = Ui_MainWindow()
window.show()
app.exec()

问题很简单,如果我单击添加按钮,小部件将被添加并且一切正常,如果我双击 QTextEdit,它的应用按钮将显示并且它将从只读变为可编辑。

单击应用按钮后,该按钮将隐藏,相应的 QTextEdit 将再次只读。

我终于设法向 QTextEdit 添加了一个 doubleClicked 信号。

而且,问题是,如果我单击删除按钮,而不是按预期删除内容,它会使整个应用程序崩溃。

很抱歉,最小可重现的例子太长了,但我只设法用所有代码重现了这个问题,我真的不知道出了什么问题。

那么如何解决呢?

标签: pythonpython-3.xpyqtpyqt6

解决方案


好吧,我已经弄清楚了,这是由我添加到每个使用固定大小按钮的水平布局中的间隔项引起的。

所有此类布局都使用相同的间隔项,完全相同的间隔项,而不仅仅是相同。

根据我的观察,间隔项都是对同一个对象的引用,它们都是同一个对象的回声,它们位于固定的内存地址。

老实说,我不明白这样的事情是如何工作的,同一个对象不仅可以添加到多个布局中并同时出现在所有布局中,还可以多次添加到同一个布局中,但它始终是原始对象,不是自身的重复。

我想当我将相同的间隔项添加到多个布局时,我没有添加原始间隔项,而是添加了相同但位于不同内存地址的原始项的副本,显然这不是 Python 的工作方式。

因此,当我删除一个小部件时,其中的所有内容,其布局中的所有内容都将被删除,并且间隔项也将被删除。

因为所有布局中的所有间隔项都是对原始间隔项的引用,所以当我删除其中一个布局时,原始间隔项也被删除,并且间隔项从所有其他布局中删除,但它的阴影仍然存在,并且该项目没有从所有其他布局中正确删除,布局包含对不再存在的对象的引用,因此应用程序崩溃了。

通过删除分隔项的定义并将添加分隔项替换为.addStretch(),修复了该错误。


推荐阅读