简体   繁体   中英

PyQT 5 Add widgets dynamically to a spoiler

I am writing a small program and ideally I want to have a fixed size of groups which can unfold in which I have further spoilers which represent items which i can open and close in order to add some entities to my system.

I have been looking for similar questions here and got to the following to work semiproperly:

在此处输入图片说明

I have added the - Buttons to remove those childs from the groups and a + to add childs to a group.

This seems to work fine as long as I am not removing or adding widgets.

My code looks like this:

spoilers.py

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtGui import *
from PyQt5.QtCore import *


class Spoiler(QWidget):
    def __init__(self, parent=None, title='', animationDuration=300, addRemoveOption='None'):
        super(Spoiler, self).__init__(parent=parent)

        self.animationDuration = animationDuration
        self.toggleAnimation = QParallelAnimationGroup()
        self.contentArea = QScrollArea()
        self.headerLine = QFrame()
        self.toggleButton = QToolButton()
        self.mainLayout = QGridLayout()
        self.childWidgets = []

        self.toggleButton.setStyleSheet("QToolButton { border: none; }")
        self.toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.toggleButton.setArrowType(Qt.RightArrow)
        self.toggleButton.setText(str(title))
        self.toggleButton.setCheckable(True)
        self.toggleButton.setChecked(False)

        self.addRemoveOperation = addRemoveOption
        if addRemoveOption is not 'None':
            if addRemoveOption is 'Add':
                self.addRemoveButton = QPushButton('+')
            else:
                self.addRemoveButton = QPushButton('-')
            self.addRemoveButton.clicked.connect(self.onAddRemoveButton)


        self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
        self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        # start out collapsed
        self.contentArea.setMaximumHeight(0)
        self.contentArea.setMinimumHeight(0)
        # let the entire widget grow and shrink with its content
        self.toggleAnimation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
        self.toggleAnimation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
        self.toggleAnimation.addAnimation(QPropertyAnimation(self.contentArea, b"maximumHeight"))
        # don't waste space
        self.mainLayout.setVerticalSpacing(0)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1, Qt.AlignLeft)
        if addRemoveOption is not 'None':
            self.mainLayout.addWidget(self.addRemoveButton, 0, 2, 1, 1, Qt.AlignRight)
        self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 3)
        self.setLayout(self.mainLayout)

        def start_animation(checked):
            arrow_type = Qt.DownArrow if checked else Qt.RightArrow
            direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward
            self.toggleButton.setArrowType(arrow_type)
            self.toggleAnimation.setDirection(direction)
            self.toggleAnimation.start()

        self.toggleButton.clicked.connect(start_animation)
        self.contentLayout = QVBoxLayout()
        self.setContentLayout(self.contentLayout)

    def setContentLayout(self, contentLayout):
        self.contentArea.destroy()
        self.contentArea.setLayout(contentLayout)
        collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
        contentHeight = contentLayout.sizeHint().height()
        for i in range(self.toggleAnimation.animationCount()-1):
            spoilerAnimation = self.toggleAnimation.animationAt(i)
            spoilerAnimation.setDuration(self.animationDuration)
            spoilerAnimation.setStartValue(collapsedHeight)
            spoilerAnimation.setEndValue(collapsedHeight + contentHeight)
        contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
        contentAnimation.setDuration(self.animationDuration)
        contentAnimation.setStartValue(0)
        contentAnimation.setEndValue(contentHeight)


    def addChild(self, child):
        self.childWidgets += [child]
        self.contentLayout.addWidget(child)
        self.setContentLayout(self.contentLayout)

    def removeChild(self, child):
        self.childWidgets.remove(child)
        #self.contentLayout.removeWidget(child)
        child.destroy()
        #self.setContentLayout(self.contentLayout)

    def onAddRemoveButton(self):
        self.addChild(LeafSpoiler(root=self))



class LeafSpoiler(Spoiler):
    def __init__(self, parent=None, root=None, title=''):
        if(root == None):
            addRemoveOption = 'None'
        else:
            addRemoveOption = 'Sub'
        super(LeafSpoiler, self).__init__(parent=parent, title=title, addRemoveOption=addRemoveOption)
        self.root = root

    def onAddRemoveButton(self):
        self.root.removeChild(self)
gui.py

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtGui import *
from PyQt5.QtCore import *

from spoilers import *


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):

        self.setGeometry(300, 300, 300, 220)
        # self.setWindowIcon(QIcon('web.png'))

        self.centralWidget = QFrame()
        self.centralLayout = QVBoxLayout()
        self.centralWidget.setLayout(self.centralLayout)

        self.spoiler1 = Spoiler(addRemoveOption='Add', title='Group 1')
        self.spoiler2 = Spoiler(addRemoveOption='Add', title='Group 2')
        for i in range(3):
            leaf = LeafSpoiler(root=self.spoiler1)
            self.spoiler1.addChild(leaf)

            leaf = LeafSpoiler(root=self.spoiler2)
            self.spoiler2.addChild(leaf)
        self.centralLayout.addWidget(self.spoiler1)
        self.centralLayout.addWidget(self.spoiler2)

        self.setCentralWidget(self.centralWidget)
        self.show()



if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = MainWindow()
    sys.exit(app.exec_())


I am not quiet sure why this doesnt work. I assume that Spoiler.setContentLayout() is not supposed to be called more than once.

I would be very happy if someone could help me out on this one!

Greetings, Finn

I am not too sure whether I understood your question correctly. I assume you are talking about pyqt crashing when trying to remove a Spoilerleaf? At least this is what's happening on my machine.

Your "removeChild" method seems to be a culprit here. Without knowing too much about the source of the crash, replacing this with a call to deleteLater() enables child deletion on my machine:

class LeafSpoiler(Spoiler):
    # [...] same init as your's

    def onAddRemoveButton(self):
        self.deleteLater()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM