PyQt中动态添加和移除小部件

15

我正在使用PyQt创建一个界面,希望能够动态添加或删除部件。我想为将要添加或删除的部件定义一个单独的类。但是我似乎无法让我实例化的部件显示在主界面内。以下是我正在使用的代码:

from PyQt4 import QtGui, QtCore
import sys

class Main(QtGui.QMainWindow):
    def __init__(self, parent = None):
        super(Main, self).__init__(parent)

        # central widget
        self.centralWidget = QtGui.QWidget(self)

        # main layout
        self.vLayout = QtGui.QVBoxLayout(self.centralWidget)

        # main button
        self.pButton_add = QtGui.QPushButton(self.centralWidget)
        self.pButton_add.setText('button to add other widgets')

        # scroll area
        self.scrollArea = QtGui.QScrollArea(self.centralWidget)
        self.scrollArea.setWidgetResizable(True)

        # scroll area widget contents
        self.scrollAreaWidgetContents = QtGui.QWidget(self.scrollArea)

        # scroll area widget contents - layout
        self.formLayout = QtGui.QFormLayout(self.scrollAreaWidgetContents)

        self.scrollArea.setWidget(self.scrollAreaWidgetContents)

        # add all main to the main vLayout
        self.vLayout.addWidget(self.pButton_add)
        self.vLayout.addWidget(self.scrollArea)
        # set central widget
        self.setCentralWidget(self.centralWidget)
        # connections
        self.pButton_add.clicked.connect(self.addWidget)


    def addWidget(self):
        z = Test(self.scrollAreaWidgetContents)
        count = self.formLayout.rowCount()
        self.formLayout.setWidget(count, QtGui.QFormLayout.LabelRole, z)


class Test(QtGui.QWidget):
  def __init__( self, parent):
      super(Test, self).__init__(parent)

      self.pushButton = QtGui.QPushButton(self)


app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()

问题在于,当我在我的“addWidget”方法中使用下面的代码时,它确切地做了我想要的事情,但是类方法似乎无法工作。

z = QtGui.QPushButton(self.scrollAreaWidgetContents)
count = self.formLayout.rowCount())
self.formLayout.setWidget(count, QtGui.QFormLayout.LabelRole, z)

我想知道为什么 z = Test() 没有产生任何结果?有什么想法吗?谢谢!

3个回答

20

实际上,它是有效的。问题在于,您的Test小部件具有一个没有布局管理的QPushButton。因此,它无法计算其minimumSize时考虑到按钮。当您将该小部件放入布局中时,它会缩小为0(因为QWidget没有默认的minimumSize),因此您看不到任何内容。

您有两个选择,要么手动管理布局并进入无谓的痛苦世界,要么依赖布局管理器。总的来说,您应该更喜欢后者。

我会像这样重新编写您的脚本(尽管我不确定为什么要使用QFormLayout,但我将其保留为原样):

from PyQt4 import QtGui, QtCore
import sys

class Main(QtGui.QMainWindow):
    def __init__(self, parent = None):
        super(Main, self).__init__(parent)

        # main button
        self.addButton = QtGui.QPushButton('button to add other widgets')
        self.addButton.clicked.connect(self.addWidget)

        # scroll area widget contents - layout
        self.scrollLayout = QtGui.QFormLayout()

        # scroll area widget contents
        self.scrollWidget = QtGui.QWidget()
        self.scrollWidget.setLayout(self.scrollLayout)

        # scroll area
        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setWidget(self.scrollWidget)

        # main layout
        self.mainLayout = QtGui.QVBoxLayout()

        # add all main to the main vLayout
        self.mainLayout.addWidget(self.addButton)
        self.mainLayout.addWidget(self.scrollArea)

        # central widget
        self.centralWidget = QtGui.QWidget()
        self.centralWidget.setLayout(self.mainLayout)

        # set central widget
        self.setCentralWidget(self.centralWidget)

    def addWidget(self):
        self.scrollLayout.addRow(Test())


class Test(QtGui.QWidget):
  def __init__( self, parent=None):
      super(Test, self).__init__(parent)

      self.pushButton = QtGui.QPushButton('I am in Test widget')

      layout = QtGui.QHBoxLayout()
      layout.addWidget(self.pushButton)
      self.setLayout(layout)



app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()

1
感谢您详细的回答!我使用Form Layout的原因是我希望小部件从滚动区域的顶部开始出现,并从那里填充空白处,而不是在中间出现并在创建新小部件时重新排列。虽然我相信还有其他方法可以实现这一点,但对我来说,Form Layout似乎是一个简单的解决方案。再次感谢! - boundless
@boundless:啊,我明白了。我的方法是将scrollLayout包装在另一个QVBoxLayout中,并在下面添加一个拉伸值为1。这样应该可以保持在顶部。 - Avaris
另外一个我喜欢formlayout的原因是,它使得两个小部件并排放置变得更容易,同时也能保持在顶部。不过也许到那时候我应该使用网格布局。再次感谢提示! - boundless
@Avaris 有没有办法在按钮点击时调用一个 .py 文件并将其加载到框架中。该 .py 文件包含一些属性和按钮。因此,在按钮点击时,我希望能够将文件加载到框架中。 - Valla
非常感谢!我试图解决这个问题已经1周了。真的非常感谢。 - Lixt

4

这是一个小改动,使得按钮在点击后自行删除:

from PyQt4 import QtGui, QtCore
import sys

class Main(QtGui.QMainWindow):
    def __init__(self, parent = None):
        super(Main, self).__init__(parent)

        # main button
        self.addButton = QtGui.QPushButton('button to add other widgets')
        self.addButton.clicked.connect(self.addWidget)

        # scroll area widget contents - layout
        self.scrollLayout = QtGui.QFormLayout()

        # scroll area widget contents
        self.scrollWidget = QtGui.QWidget()
        self.scrollWidget.setLayout(self.scrollLayout)

        # scroll area
        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setWidget(self.scrollWidget)

        # main layout
        self.mainLayout = QtGui.QVBoxLayout()

        # add all main to the main vLayout
        self.mainLayout.addWidget(self.addButton)
        self.mainLayout.addWidget(self.scrollArea)

        # central widget
        self.centralWidget = QtGui.QWidget()
        self.centralWidget.setLayout(self.mainLayout)

        # set central widget
        self.setCentralWidget(self.centralWidget)

    def addWidget(self):
        self.scrollLayout.addRow(TestButton()) 


class TestButton(QtGui.QPushButton):
  def __init__( self, parent=None):
      super(TestButton, self).__init__(parent)
      self.setText("I am in Test widget")
      self.clicked.connect(self.deleteLater)


app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()

这样做可以避免删除时的内存泄漏,并且按钮实际上可以用于其他事情。我按照此模式制作了许多下载和块监控的进度条,即使使用线程和多处理,也能正常工作。而不是简单的QThreads...


4
你改了哪一部分,为什么会产生这种影响? - Andy Hayden

2

如果您想在程序中的任何事件期间,例如按钮被点击时删除小部件,请使用deleteLater()方法:self.yourwidget.deleteLater()

self.button.clicked.connect(delete_widget);

def delete_widget(self):
     self.widget.deleteLater();

使用上述函数可以使该小部件从应用程序中消失

self.widget.deleteLater();


基于 Wow。终于有一个不到 100 行代码的解决方案来解决这个问题了。 - Leo
self.button.clicked.connect(self.button.deleteLayer); 应该足够了。 - bugmenot123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接