清除pyqt布局中的所有小部件

Posted

技术标签:

【中文标题】清除pyqt布局中的所有小部件【英文标题】:Clear all widgets in a layout in pyqt 【发布时间】:2011-05-30 12:36:18 【问题描述】:

有没有办法清除(删除)布局中的所有小部件?

self.plot_layout = QtGui.QGridLayout()
self.plot_layout.setGeometry(QtCore.QRect(200,200,200,200))
self.root_layout.addLayout(self.plot_layout)
self.plot_layout.addWidget(MyWidget())

现在我想用一个新的小部件替换plot_layout 中的小部件。有没有一种简单的方法可以清除plot_layout 中的所有小部件?我没有看到任何这样的方法。

【问题讨论】:

【参考方案1】:

经过大量研究(这个花费了相当长的时间,所以我将其添加到这里以供将来参考),这是我发现真正清除和删除布局中的小部件的方式:

for i in reversed(range(layout.count())): 
    layout.itemAt(i).widget().setParent(None)

文档中关于 QWidget 的内容是:

新小部件在其父部件被删除时被删除。

重要提示:您需要向后循环,因为从头开始删除内容会移动项目并更改布局中项目的顺序。

测试并确认布局为空:

for i in range(layout.count()): print i

似乎还有另一种方法可以做到这一点。不要使用 setParent 函数,而是使用 deleteLater() 函数,如下所示:

for i in reversed(range(layout.count())): 
    layout.itemAt(i).widget().deleteLater()

文档说 QObject.deleteLater (self)

计划删除此对象。

但是,如果您运行上面指定的测试代码,它会打印一些值。这表明布局仍然有项目,而不是带有 setParent 的代码。

【讨论】:

for i in reversed(range(layout.count()-1)): 是正确的以防止越界错误 您应该使用takeAt() 而不是itemAt()takeAt() 还将从 QBoxLayout 中删除间隔/拉伸(没有父级):widget = layout.takeAt(i).widget(); if widget is not None: widget.setParent(None) 您确定setParent(None) 方法吗?我不认为这会删除小部件。 “新小部件在其父级被删除时被删除。”对我来说,当它被父级调用时,听起来像是对 deleteLater() 的递归调用。 我写了a simple test 表明 setParent(None) 不会删除小部件。即使该命令以清除布局的方式回答问题,也不建议使用。这会导致内存泄漏!【参考方案2】:

这可能有点太晚了,但只是想添加这个以供将来参考:

def clearLayout(layout):
  while layout.count():
    child = layout.takeAt(0)
    if child.widget():
      child.widget().deleteLater()

改编自 Qt 文档http://doc.qt.io/qt-5/qlayout.html#takeAt。请记住,当您在 while 或 for 循环中从布局中删除子项时,您实际上是在修改布局中每个子项的索引 #。这就是为什么使用for i in range() 循环会遇到问题的原因。

【讨论】:

我假设您的意思是 child.widget().deleteLater()... 非常感谢 - 似乎是一个相当优雅的解决方案。如果 deleteLater() 给我带来任何麻烦,我可以按照其他人的建议尝试 close() 或 setParent(None)。【参考方案3】:

如果您不需要将新的小部件添加到布局中,那么 PALEN 的答案很有效。

for i in reversed(range(layout.count())): 
    layout.itemAt(i).widget().setParent(None)

但是,如果您多次清空并填充布局或使用许多小部件,您将在某些时候收到“分段错误(核心转储)”。布局似乎保留了一个小部件列表,并且该列表的大小有限。

如果您以这种方式删除小部件:

for i in reversed(range(layout.count())): 
    widgetToRemove = layout.itemAt(i).widget()
    # remove it from the layout list
    layout.removeWidget(widgetToRemove)
    # remove it from the gui
    widgetToRemove.setParent(None)

你不会遇到这个问题。

【讨论】:

非常感谢,在清除和添加更多项目后,我得到“进程完成,退出代码 139”,这让我抓狂。 setParent(None) 不会删除小部件。请参阅我的 cmets 和关于palens 的答案。这也是Segmentation fault 的原因。请改用deleteLater() python widget.setAttribute(QtCore.Qt.WA_DeleteOnClose) widget.close() gridLayout.removeWidget(widget) 它非常适合我【参考方案4】:

这就是我清除布局的方式:

def clearLayout(layout):
    if layout is not None:
        while layout.count():
            child = layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            elif child.layout() is not None:
                clearLayout(child.layout())

【讨论】:

【参考方案5】:

可以使用widgetclose()方法:

for i in range(layout.count()): layout.itemAt(i).widget().close()

【讨论】:

【参考方案6】:

我用:

    while layout.count() > 0: 
        layout.itemAt(0).setParent(None)

【讨论】:

对于我的 PyQT 版本,必须在中间添加一个 .widget() 调用:layout.itemAt(0).widget().setParent(None)【参考方案7】:

我对这个问题的解决方案是重写QWidget的setLayout方法。以下代码将布局更新为可能包含也可能不包含已显示项目的新布局。您可以简单地创建一个新的布局对象,添加任何您想要的内容,然后调用 setLayout。当然,你也可以只调用 clearLayout 来删除所有内容。

def setLayout(self, layout):
    self.clearLayout()
    QWidget.setLayout(self, layout)

def clearLayout(self):
    if self.layout() is not None:
        old_layout = self.layout()
        for i in reversed(range(old_layout.count())):
            old_layout.itemAt(i).widget().setParent(None)
        import sip
        sip.delete(old_layout)

【讨论】:

【参考方案8】:

来自文档:

要从布局中删除小部件,请调用 removeWidget()。在小部件上调用 QWidget.hide() 也会有效地将小部件从布局中移除,直到调用 QWidget.show()

removeWidget 继承自 QLayout,这就是为什么它没有被列在 QGridLayout 方法中。

【讨论】:

但 removeWidget() 将小部件作为参数 removeWidget (self, QWidget w)。我没有对小部件的引用。 @Falmarri: grid.itemAt(idx).widget() 按索引。【参考方案9】:

这是我第一次真正回答堆栈溢出问题,但我看到这里的所有答案都略有错误。 (是的,我知道问题是删除所有小部件)它们中的大多数问题是它们不考虑嵌套布局,所以我做了一个递归函数,给定一个布局,它将递归删除其中的所有内容,以及里面的所有布局。在这里:

def clearLayout(layout):
print("-- -- input layout: "+str(layout))
for i in reversed(range(layout.count())):
    layoutItem = layout.itemAt(i)
    if layoutItem.widget() is not None:
        widgetToRemove = layoutItem.widget()
        print("found widget: " + str(widgetToRemove))
        widgetToRemove.setParent(None)
        layout.removeWidget(widgetToRemove)
    elif layoutItem.spacerItem() is not None:
        print("found spacer: " + str(layoutItem.spacerItem()))
    else:
        layoutToRemove = layout.itemAt(i)
        print("-- found Layout: "+str(layoutToRemove))
        clearLayout(layoutToRemove)

我可能没有考虑所有 UI 类型,不确定。希望这会有所帮助!

【讨论】:

支持递归版本。我也喜欢你添加了很多其他答案省略的无检查。我也遇到了在 setParents 之前调用 removeWidget 的问题,就像其他人建议的那样。 话虽如此,我认为这段代码可以清理一下。如果我正确阅读了其他 cmets,那么使用 takeAt() 而不是 itemAt() 将隐式处理间隔,因此您可能会摆脱 elif 分支。另一件事是您调用 widget() 和 itemAt() 的次数超出了您的需要。调用它们一次并使用变量将使代码更具可读性。 还有一件事:我认为你的变量名有点不对劲。当您调用itemAt() 时,结果变量应该调用item,而不是layout,因为该函数返回QLayoutItem,而不是QLayout。这也使您的函数实际期望的输入有点令人困惑。您的输入变量名为layout,但它确实需要QLayoutItem,对吧?命名在这里很重要,以使代码更易于理解。【参考方案10】:

如果您使用堆叠的小部件在已知视图之间交换并且仅翻转显示的索引可能比从布局中添加和删除单个小部件容易得多。

如果您想替换 所有 小部件的子级,那么 QObject 函数 findChildren 应该可以让您到达那里,例如我不知道模板函数是如何包装在 pyqt 中的。但是,如果您知道小部件,也可以按名称搜索它们。

【讨论】:

嗯,这是我的第一个 Qt 应用程序(除了搞乱它),所以我确信我做很多事情效率很低。因为不是plot_layout 一次只能有 1 个小部件(我将其设置为网格布局以供将来扩展)。它拥有的小部件是扩展 matplotlib 的 FigureCanvasQTAgg 的自定义小部件。我有很多情节,我想做的是当用户单击树视图中的情节(这是有效的)时,我想显示该情节。 也许将绘图代码从FigureCanvasQTAgg 子类移动到一个单独的位置可能是一个更好的主意,只需根据请求更新给定的Figure。然后,您可以简单地重新绘制绘图,而不是弄乱小部件和布局。示例代码见eli.thegreenplace.net/files/prog_code/qt_mpl_bars.py.txt。【参考方案11】:
        for i in reversed (range(layout.count())):
            layout.itemAt(i).widget().close()
            layout.takeAt(i)

        for i in range(layout.count()):
            layout.itemAt(0).widget().close()
            layout.takeAt(0)

【讨论】:

【参考方案12】:

我对前面提到的解决方案有疑问。有一些挥之不去的小部件导致了问题;我怀疑已安排删除,但未完成。我还必须将小部件父级设置为无。 这是我的解决方案:

def clearLayout(layout):
    while layout.count():
        child = layout.takeAt(0)
        childWidget = child.widget()
        if childWidget:
            childWidget.setParent(None)
            childWidget.deleteLater()

【讨论】:

【参考方案13】:
for i in reversed(range(layout.count())):
    if layout.itemAt(i).widget():
        layout.itemAt(i).widget().setParent(None)
    else:
        layout.removeItem(layout.itemAt(i))

【讨论】:

以上是关于清除pyqt布局中的所有小部件的主要内容,如果未能解决你的问题,请参考以下文章

PyQt4 - 自定义小部件类结构?

PyQt5 - 动态添加小部件到布局

尽管更新了小部件,但 Pyqt5 更新的小部件未添加到布局中

在 PyQt5/Pyside2 中动态添加小部件到流布局

PyQT - 在特定选项卡中引用小部件

添加/删除包含多个小部件pyqt的布局