0

I am using Python 3.9.5.

I have encountered some serious problem in my project and here is a minimum reproducible example code, along with some descriptions.

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()

The problem is very simple, if I click add button, the widget will be added and everything works fine, if I double click on a QTextEdit, its apply button will show and it will change from read only to editable.

After I click an apply button, the button will hide and the corresponding QTextEdit will be read only again.

I have finally managed to add a doubleClicked signal to QTextEdit.

And, the problem, is if I click delete button, instead of deleting the thing as expected, it crashes the whole application.

I am sorry the minimum reproducible example is so long, but I have only managed to reproduce the issue with all the code and I really don't know what went wrong.

So how to fix it?

Ξένη Γήινος
  • 2,181
  • 1
  • 9
  • 35
  • 2
    Hello and welcome to StackOverflow. Please try to keep your question and their code to a minimum. Your post contains lots of information that is really unimportant to what you're asking (the themeing, the visual layout, how you searched for solutions), which makes the whole question very confusing: there's so many information there that's actually hard to understand where the problem actually is described. Please [edit] your post, make it simpler by going straight to the point avoiding irrelevant info, and provide a [mre] that we could actually copy, paste and run without any modifications. – musicamante Jul 05 '21 at 14:17
  • 1
    Btw, some offtopic but still relevant suggestions. 1. the arguments of `addLayout` and `addWidget` of a grid layout are cell *units*, so using values high as you did (25, 75, 100) is pointless (and conceptually wrong). If you want to set the correct proportions between items, use [stretch factors](https://doc.qt.io/qt-5/layout.html#stretch-factors). 2. while technically possible, you should avoid adding the *same* spacer item to a layout; and, in any case, you added a 0-width spacer, so it's almost useless since you're setting fixed sizes for buttons, as the layout will take care of that. – musicamante Jul 05 '21 at 14:36
  • 1
    3. avoid arbitrary settings of absolute geometries based on the screen; you're assuming that the screen is actually bigger than the UI, but that is not guaranteed: in my computer the result is that a good portion of the window is off-screen. 4. class names should always start with an uppercase letter, otherwise it would be difficult to distinguish them in normal code usage; read more on the [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/). 5. there's little to no benefit in having a class defined in a function, and that's usually the result of poor object structure. – musicamante Jul 05 '21 at 14:41

1 Answers1

0

Well, I have figured it out, it was caused by the spacer item that I add to every one of horizontal layouts where fixed-sized buttons are used.

All such layouts are using the same spacer item, exactly the same spacer item, not just identical.

From what I have observed, the spacer items are all references to the same object, they are all echos of the same object which lives at a fixed memory address.

I honestly don't understand how such thing works, that the same object can not only be added to multiple layouts and present in all the layouts concurrently, but also be added to the same layout multiple times, yet it always remains the original object, not duplicates of itself.

I thought when I added the same spacer item to multiple layouts, I didn't add the original spacer item, instead duplicates of the original item which are identical but are at different memory addresses, and clearly that isn't how Python works.

So when I remove a widget, everything inside it, everything inside its layout is deleted, and the spacer item is deleted.

Because all the spacer items in all the layouts are references to the original spacer item, when I delete one of the layouts, the original spacer item is deleted as well, and the spacer item is deleted from all other layouts, yet its shadows remain, and the item isn't properly removed from all the other layouts, the layouts contain references to an object that no longer exists, thus the application crashed.

By removing the definition of the spacer item and replacing adding the spacer item with .addStretch(), the bug is fixed.

Ξένη Γήινος
  • 2,181
  • 1
  • 9
  • 35
  • QSpacerItem is an *abstract* object that represents a blank space in a layout, while, due to its nature, it's possible to add *the same* item multiple times, it's not suggested (actually, as the [QSpacerItem docs](https://doc.qt.io/qt-5/qspaceritem.html#details) say, "Normally, you don't need to use this class directly"). "I thought [..] I didn't add the original spacer item, instead duplicates [..] identical but are at different memory addresses". No, a reference to an object is always to the same object, no "new objects at different address" are created. This is not related to Python. – musicamante Jul 07 '21 at 14:23