如何清除父控件中的所有小部件?

23

我正在使用构造函数QWidget(QWidget *parent)。这个父窗口包含许多子窗口。我需要在运行时清除所有子窗口。我应该怎么做?


回答问题https://dev59.com/PG855IYBdhLWcg3wjlKL#18496300可能是相关的。 - Alex S
5个回答

28

之前的答案是错误的!!你不能使用 findChildren 来删除一个 widget 的子控件,因为 Qt4 的 findChildren递归地 列出所有子控件。因此,你会删除子控件的子控件,这可能会导致应用程序崩溃。

更一般地说,在 Qt 中,将 QObject 指针列表逐个删除是危险的,因为销毁一个对象可能会级联销毁其他对象,这是由于父对象拥有机制或将 destroyed() 信号连接到 deleteLater() 插槽所致。因此,删除列表中的第一个对象可能会使下一个对象失效。

你需要按照以下方式列出子控件:

  • 如果你在使用 Qt5(当时发问时还不存在),可以给 findChild 函数传递 Qt::FindDirectChildrenOnly 标志来获取直接子控件;
  • 使用 QLayout 函数来列出项目;
  • 使用 QObject::children,对于每个对象,用 isWidgetType() 进行测试或进行转换以检测其是否为控件;
  • 使用循环中的 findChild() 并删除结果,直到它返回一个空指针。

4
你可以在 findChildren 上使用Qt::FindDirectChildrenOnly 以避免双重删除。 - Drew McGowen
@DrewMcGowen:没错,但这只是自Qt5以来的情况。 - galinette
@DrewMcGowen,你应该发布一个带有示例的答案。 - Assimilater
1
这个答案忽略了一个简单的事实,即findChildren可以使用Qt::FindDirectChildrenOnly进行调用。这非常具有误导性。 - Kuba hasn't forgotten Monica
qDeleteAll 使用 delete 删除容器中的项目。手动循环并调用 deleteLater 是否更合适/更安全? - GPhilo
显示剩余2条评论

24
为了解决@galinette指出的递归问题,您可以在while循环中删除小部件。
while ( QWidget* w = findChild<QWidget*>() )
    delete w;

2
这是我认为最合适的答案。 - Michal
1
如果您正在使用Qt5(现在应该是这样),请查看https://dev59.com/eW865IYBdhLWcg3wLrlE#35802122,答案更加完整。但是这段代码仍然有效。 - Matthias Kuhn
这仍然似乎是Qt 4版本的最佳答案。根据QObject :: findChild()的文档(https://doc.qt.io/archives/qt-4.8/qobject.html#findChild),如果有多个子项[sic,但他们真正意思是后代]符合搜索条件,则返回*最直接* [我强调]祖先[sic,但他们真正意思是后代]。因此,直接子窗口小部件肯定会首先被销毁,然后将递归地销毁该下面的更间接后代小部件。 - Mike Finch

11

总结和补充:

对于Qt5,简单概括:

qDeleteAll(parentWidget->findChildren<QWidget*>("", Qt::FindDirectChildrenOnly));

对于许多孩子来说,使用setUpdatesEnabled()的Qt5:

parentWidget->setUpdatesEnabled(false);
qDeleteAll(parentWidget->findChildren<QWidget*>("", Qt::FindDirectChildrenOnly));
parentWidget->setUpdatesEnabled(true);

请注意,这不是异常安全的!尽管Qt现在似乎没有在这里抛出异常,但destroyed()信号可能已连接到会抛出异常的代码,或者重写的Object::childEvent(QChildEvent*)可能会抛出异常。
更好的方法是使用一个辅助类:
class UpdatesEnabledHelper
{
    QWidget* m_parentWidget;
public:
    UpdatesEnabledHelper(QWidget* parentWidget) : m_parentWidget(parentWidget) { parentWidget->setUpdatesEnabled(false); }
    ~UpdatesEnabledHelper() { m_parentWidget->setUpdatesEnabled(true); }
};

...

UpdatesEnabledHelper helper(parentWidget);
qDeleteAll(parentWidget->findChildren<QWidget*>("", Qt::FindDirectChildrenOnly));

对于Qt4:

QList<QWidget*> childWidgets = parentWidget->findChildren<QWidget*>();
foreach(QWidget* widget, childWidgets)
    if (widget->parentWidget() == parentWidget)
        delete widget;

从QLayout中移除控件在Qt4和Qt5中都可行:

QLayoutItem* child;
while (NULL != (child = layout->takeAt(0))) // or nullptr instead of NULL
    delete child;

QObject(因此也包括QWidgets)在其(QObject)析构函数中自动从其父对象中删除自己。


对我而言(Qt 5.12.6),findChildren<QWidget*>("", Qt::FindDirectChildrenOnly) 找不到任何东西,但是 findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly) 可行。 - Sasha
1
@Sasha 但是对于Qt 5.15可以工作。 - S.M.Mousavi

6

来自Qt 文档

下面的代码片段展示了一种安全的方式去除布局中的所有项:

QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
    ...
    delete child;
}

-6
您可以在父组件类中使用以下内容:
QList<QWidget *> widgets = findChildren<QWidget *>();
foreach(QWidget * widget, widgets)
{
    delete widget;
}

9
Qt 还提供了一个叫做 qDeleteAll 的函数,它接受一个容器并删除容器中的每个项。因此,你可以简化为 qDeleteAll(findChildren<QWidget*>()); - Caleb Huitt - cjhuitt
3
如果有很多孩子,你可能也需要考虑设置 setUpdatesEnabled(false); qDeleteAll(findChildren<QWidget*>()); setUpdatesEnabled(true); - MadH
这个答案是错误的,它只在子小部件没有自己的子项时才有效。否则,孙子将被删除两次。因为findChildren递归列出所有子项、孙子等。 - galinette

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