Qt - 从布局中删除所有小部件?

Posted

技术标签:

【中文标题】Qt - 从布局中删除所有小部件?【英文标题】:Qt - remove all widgets from layout? 【发布时间】:2010-11-24 22:37:09 【问题描述】:

这似乎并不容易。基本上,我通过函数将QPushButtons 添加到布局中,当函数执行时,我想先清除布局(删除所有QPushButtons 以及其中的任何其他内容),因为更多按钮只是被附加到scrollview

标题

QVBoxLayout* _layout;

cpp

void MainWindow::removeButtonsThenAddMore(const QString &item) 

//remove buttons/widgets

QVBoxLayout* _layout = new QVBoxLayout(this);

QPushButton button = new QPushButton(item);
_layout->addWidget(button);

QPushButton button = new QPushButton("button");
_layout->addWidget(button);

QWidget* widget = new QWidget();
widget->setLayout(_layout);

QScrollArea* scroll = new QScrollArea();
scroll->setWidget(widget);
scroll->show();


【问题讨论】:

为什么不将稍后将被删除的所有内容添加到小部件(其***布局或其他任何内容的边距为零),将此(临时)小部件添加到您的布局中,完成后,只需 @ 987654326@ it(这将把它连同它的所有布局、小部件和垫片一起删除)? @mlvljr 的这条评论显然是最好的答案。简单、模块化,这是很好的 OOD,可以防止其他答案可能引入的许多错误。 @ymoreau 非常感谢您的称赞——如果您想在啤酒中讨论 OOD(包括 Qt 应用程序),请随时给我发电子邮件(我的地址在个人资料中)一天:) 【参考方案1】:

我遇到了同样的问题:我有一个游戏应用程序,它的主窗口类继承了 QMainWindow。它的构造函数部分看起来像这样:

m_scene = new QGraphicsScene;
m_scene->setBackgroundBrush( Qt::black );
...
m_view = new QGraphicsView( m_scene );
...
setCentralWidget( m_view );

当我想显示游戏的关卡时,我实例化了一个 QGridLayout,我在其中添加了 QLabel,然后将它们的像素图设置为某些图片(具有透明部分的像素图)。第一层显示正常,但是当切换到第二层时,仍然可以看到第一层的像素图在新的像素图后面(像素图是透明的)。

我尝试了几种方法来删除旧的小部件。 (a) 我尝试删除 QGridLayout 并实例化一个新的,但后来了解到删除布局不会删除添加到其中的小部件。 (b) 我尝试在新像素图上调用 QLabel::clear() ,但这当然只对新像素图有影响,对僵尸像素图没有影响。 (c) 我什至尝试删除我的 m_view 和 m_scene,并在每次显示新关卡时重建它们,但仍然没有运气。

然后(d)我尝试了上面给出的解决方案之一,即

QLayoutItem *wItem;
while (wItem = widget->layout()->takeAt(0) != 0)
    delete wItem;

但这也没用。

但是,进一步谷歌搜索,我找到了一个有效的答案。 (d) 中缺少的是对delete item->widget() 的调用。以下现在对我有用:

// THIS IS THE SOLUTION!
// Delete all existing widgets, if any.
if ( m_view->layout() != NULL )

    QLayoutItem* item;
    while ( ( item = m_view->layout()->takeAt( 0 ) ) != NULL )
    
        delete item->widget();
        delete item;
    
    delete m_view->layout();

然后我像第一级一样实例化一个新的 QGridLayout,将新级别的小部件添加到其中,等等。

Qt 在很多方面都很棒,但我确实认为这个问题表明这里的事情可能会更容易一些。

【讨论】:

其实,等等,我可能说得太早了。我看到第二个游戏关卡显示正确,但仍有一些有趣的事情发生。一旦我弄清楚了,我会回到这个。 此解决方案未正确考虑嵌套布局。有关正确解决方案,请参阅 Darko Maksimovic 的答案。【参考方案2】:

Qt 帮助中的Layout management 页面:

布局将自动重新设置小部件(使用 QWidget::setParent()) 以便它们是其上的小部件的子级 布局已安装。

我的结论:小部件需要手动销毁或者通过销毁父WIDGET,而不是布局

布局中的小部件是在其上的小部件的子级。 布局已安装,而不是布局本身。小部件只能有 其他小部件作为父级,而不是布局。

我的结论:同上

“矛盾”致@Muelner “项目的所有权转移到布局,布局有责任删除它。” - 这并不意味着 WIDGET,而是重新设置为布局的 ITEM,稍后将被布局删除。小部件仍然是安装了布局的小部件的子级,需要手动或通过删除整个父级小部件来删除它们。

如果真的需要从布局中删除所有小部件和项目,使其完全为空,他需要创建一个这样的递归函数:

// shallowly tested, seems to work, but apply the logic

void clearLayout(QLayout* layout, bool deleteWidgets = true)

    while (QLayoutItem* item = layout->takeAt(0))
    
        if (deleteWidgets)
        
            if (QWidget* widget = item->widget())
                widget->deleteLater();
        
        if (QLayout* childLayout = item->layout())
            clearLayout(childLayout, deleteWidgets);
        delete item;
    

【讨论】:

如果小部件有信号连接到它们,这是很危险的,因为在调用 clearLayout(...) 时可能存在未决事件。 为了防止挂起事件出现这个问题,应该调用 widget->deleteLater() 而不仅仅是 delete ??【参考方案3】:

此代码删除其所有子代。所以布局内的所有东西都“消失”了。

qDeleteAll(yourWidget->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly));

这将删除小部件yourWidget 的所有直接小部件。使用Qt::FindDirectChildrenOnly必要的,因为它可以防止删除作为也在列表中的小部件的子小部件,并且可能已经被qDeleteAll 内的循环删除。

这里是qDeleteAll的描述:

void qDeleteAll(ForwardIterator begin, ForwardIterator end)

使用 C++ 删除 > 运算符删除范围 [begin, end] 中的所有项目。项类型必须是指针类型(例如,QWidget *)。

请注意,qDeleteAll 需要使用来自该小部件(而不是布局)的容器调用。请注意,qDeleteAll 不会删除 yourWidget - 只是它的子级。

【讨论】:

我有一个组框的网格布局,并且为组框调用 qDeleteAll 会使程序崩溃。我也尝试使用布局对象调用它,但它什么也没做。我将 QT 5.8 用于桌面应用程序。 这个比较方便。 @MarcoSulla 将小部件添加到布局并不会将小部件添加到布局,而是添加到布局的父级(小部件)。所以在布局上调用children() 会正确返回一个空列表。 他说的是“删除”,而不是删除(销毁对象)。要从布局中删除小部件,您无需销毁该对象。【参考方案4】:

未尝试:为什么不创建一个新布局,将其与旧布局交换并删除旧布局?这应该删除布局拥有的所有项目并保留其他项目。

编辑:在研究了我的答案、文档和 Qt 源代码后,我找到了更好的解决方案:

如果您仍然启用了 Qt3 支持,则可以使用 QLayout::deleteAllItems(),这与 QLayout::takeAt: 文档中的提示基本相同:

以下代码片段显示了从布局中删除所有项目的安全方法:

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

编辑:经过进一步研究,上面两个版本看起来是等效的:只有没有父级的子布局和小部件被删除。具有父级的小部件以特殊方式处理。看起来 TeL 的解决方案应该可以工作,您只需注意不要删除任何***小部件。另一种方法是使用小部件层次结构来删除小部件:创建一个没有父小部件的特殊小部件,并将所有可移除小部件创建为这个特殊小部件的子小部件。清理布局后删除这个特殊的小部件。

【讨论】:

这不会删除布局中已经存在的小部件,因为小部件属于父小部件,而不是布局。 如果你对某人投了反对票,你至少应该阅读 QLayout::addItem 的相应文档,其中包含注释:他的项目所有权转移到布局,它是布局的责任删除它. @hmuelner 好的,让我们阅读文档。首先,您引用的“addItem”方法未在示例中使用,并且“不建议在应用程序中使用”。其次,“~QVBoxLayout()”的文档清楚地指出“布局的小部件没有被破坏。”。看来user118657的批评是对的。 文档自相矛盾:QLayout::addWidget包含:这个函数使用addItem()。所以我去了终极文档——源代码。看起来我们俩都是对的:如果您添加一个没有父级的 wdiget(如 OP 的示例中),则布局将获得所有权并且小部件被销毁,否则小部件将在其父级被销毁时被销毁。跨度> 我刚刚查看了 QLayout::deleteAllItems()(Qt3 支持)和 QLayout::takeAt 的文档。这给出了另一个答案。【参考方案5】:

您还需要确保删除了不是 QWidget 的垫片和其他东西。如果您确定布局中唯一的东西是 QWidgets,那么前面的答案就可以了。否则你应该这样做:

QLayoutItem *wItem;
while (wItem = widget->layout()->takeAt(0) != 0)
      delete wItem;

知道如何执行此操作很重要,因为如果您要清除的布局是更大布局的一部分,您不想破坏布局。您希望确保您的布局保持其位置以及与窗口其余部分的关系。

您还应该小心,每次调用此方法时都在创建大量对象,并且它们没有被清理。首先,您可能应该在其他地方创建 QWidget 和 QScrollArea,并保留一个指向它们的成员变量以供参考。然后你的代码可能看起来像这样:

QLayout *_layout = WidgetMemberVariable->layout();

// If it is the first time and the layout has not been created
if (_layout == 0)

  _layout = new QVBoxLayout(this);
  WidgetMemberVariable->setLayout(_layout);


// Delete all the existing buttons in the layout
QLayoutItem *wItem;
while (wItem = widget->layout()->takeAt(0) != 0)
    delete wItem;

//  Add your new buttons here.
QPushButton button = new QPushButton(item);
_layout->addWidget(button);

QPushButton button = new QPushButton("button");
_layout->addWidget(button);

【讨论】:

这很好用,但我必须在下面的语句 while ((wItem = ui-&gt;verticalLayoutForScenarios-&gt;takeAt(0)) != 0) 中添加括号【参考方案6】:

您不会写到另一种方式,但您也可以只使用QStackedWidget 并为此添加两个视图,一个用于您需要的每种按钮排列。在它们两者之间切换不是问题,并且比同时处理动态创建的按钮的各种实例的风险要小得多

【讨论】:

【参考方案7】:

现有答案在我的应用程序中均无效。到目前为止,对达科·马克西莫维奇的修改似乎有效。这里是:

void clearLayout(QLayout* layout, bool deleteWidgets = true)

    while (QLayoutItem* item = layout->takeAt(0))
    
        QWidget* widget;
        if (  (deleteWidgets)
              && (widget = item->widget())  ) 
            delete widget;
        
        if (QLayout* childLayout = item->layout()) 
            clearLayout(childLayout, deleteWidgets);
        
        delete item;
    

至少在我的小部件和布局层次结构中,有必要明确地递归和删除小部件。

【讨论】:

【参考方案8】:

仅适用于我的按钮列表,如果小部件本身也被删除。否则旧按钮仍然可见:

QLayoutItem* child;
while ((child = pclLayout->takeAt(0)) != 0)

    if (child->widget() != NULL)
    
        delete (child->widget());
    
    delete child;

【讨论】:

【参考方案9】:

我有一个类似的情况,我有一个 QVBoxLayout 包含动态创建的 QHBoxLayout 对象,其中包含许多 QWidget 实例。出于某种原因,我无法通过删除*** QVBoxLayout 或单个 QHBoxLayouts 来摆脱这些小部件。我得到工作的唯一解决方案是遍历层次结构并专门删除和删除所有内容:

while(!vbox->isEmpty()) 
    QLayout *hb = vbox->takeAt(0)->layout();
    while(!hb->isEmpty()) 
        QWidget *w = hb->takeAt(0)->widget();
        delete w;
    
    delete hb;

【讨论】:

【参考方案10】:

如果你想删除所有小部件,你可以这样做:

foreach (QObject *object, _layout->children()) 
  QWidget *widget = qobject_cast<QWidget*>(object);
  if (widget) 
    delete widget;
  

【讨论】:

嗯...这强制关闭应用程序,即使我等待布局填充一次。我一定是做错了什么。 不要复制粘贴代码。在 Qt 文档中查找我上面使用的内容并使其适合您的需求。使用 gdb 或 Qt Creator 通过在调试模式下运行它来查找崩溃的位置。 QLayout 不能是 QWidget 的父级。所以 qLayout->childern() 返回 0 个项目。【参考方案11】:

我对此问题有一个可能的解决方案(请参阅Qt - Clear all widgets from inside a QWidget's layout)。分两步删除所有小部件和布局。

第 1 步:删除所有小部件

    QList< QWidget* > children;
    do
    
       children = MYTOPWIDGET->findChildren< QWidget* >();
       if ( children.count() == 0 )
           break;
       delete children.at( 0 );
    
    while ( true );

第 2 步:删除所有布局

    if ( MYTOPWIDGET->layout() )
    
        QLayoutItem* p_item;
        while ( ( p_item = MYTOPWIDGET->layout()->takeAt( 0 ) ) != nullptr )
            delete p_item;
        delete MYTOPWIDGET->layout();
    

在第 2 步之后,您的 MYTOPWIDGET 应该是干净的。

【讨论】:

【参考方案12】:

如果您在将小部件添加到布局和将布局添加到其他布局时没有做任何有趣的事情,则它们都应该在添加到其父小部件时重新设置父级。所有QObjects 都有一个deleteLater() 槽,这将导致QObject 在控制返回到事件循环后立即被删除。在这个庄园中删除的小部件也会删除它们的孩子。因此,您只需在树中最高的项目上调用deleteLater()

在 hpp 中

QScrollArea * Scroll;

在cpp中

void ClearAndLoad()
    Scroll->widget()->deleteLater();
    auto newLayout = new QVBoxLayout;
    //add buttons to new layout
    auto widget = new QWidget;
    widget->setLayout(newLayout);
    Scroll->setWidget(widget);

还请注意,在您的示例中,_layout 是一个局部变量,与头文件中的_layout 不同(删除QVBoxLayout* 部分)。另请注意,以_ 开头的名称是为标准库实现者保留的。我在var_ 中使用尾随_ 来显示局部变量,有很多口味,但前面的___ 在技术上是保留的。

【讨论】:

您确定 deleteLater() 在队列 idle 后删除吗? (以及如何定义空闲?空闲wrt。对象?不太可能......)文档状态:“当控制返回事件循环时,对象将被删除。”我在发送到 deleteLater()'d 对象的事件方面遇到了一些问题,尽管这可能是由于很多事情...... 我把措辞改成了不那么含糊的东西。 deleteLater() 的实现与从其他线程到达的其他信号/插槽一样,这些线程按出现顺序排队。因此,如果在调用 deleteLater() 时将其他信号(事件)排队,它们将在被删除之前被处理,这是设计使然。

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

QT:如何在布局中动态添加和删除小部件?

python 从布局中删除所有小部件

如何使布局填充qt选项卡小部件内的所有内容?

Qt小部件/布局的WinForm“Dock Fill”等效?

如何删除小部件上已经存在的布局?

Qt 在另一个小部件中调整小部件的大小