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

发布于 2024-10-04 11:08:54 字数 663 浏览 0 评论 0原文

这看起来并不容易。基本上,我通过函数将 QPushButton 添加到布局中,当函数执行时,我想首先清除布局(删除所有 QPushButton 以及其中的其他内容)那里),因为更多按钮会附加到滚动视图中。

标头

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();

}

This doesn't seem easy. Basically, I add QPushButtons through a function to a layout, and when the function executes, I want to clear the layout first (removing all QPushButtons and whatever else is in there), because more buttons just get appended to the scrollview.

header

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();

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

柠檬色的秋千 2024-10-11 11:08:54

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

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

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

我尝试了几种方法来删除旧的小部件。 (a) 我尝试删除 QGridLayout 并实例化一个新的 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

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

I had the same problem: I have a game app whose main window class inherits QMainWindow. Its constructor looks partly like this:

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

When I want to display a level of the game, I instantiate a QGridLayout, into which I add QLabels, and then set their pixmaps to certain pictures (pixmaps with transparent parts). The first level displays fine, but when switching to the second level, the pixmaps from the first level could still be seen behind the new ones (where the pixmap was transparent).

I tried several things to delete the old widgets. (a) I tried deleting the QGridLayout and instantiating a new one, but then learned that deleting a layout does not delete the widgets added to it. (b) I tried calling QLabel::clear() on the new pixmaps, but that of course had only an effect on the new ones, not the zombie ones. (c) I even tried deleting my m_view and m_scene, and reconstructing them every time I displayed a new level, but still no luck.

Then (d) I tried one of the solutions given above, namely

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

but that didn't work, either.

However, googling further, I found an answer that worked. What was missing from (d) was a call to delete item->widget(). The following now works for me:

// 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();
}

and then I instantiate a new QGridLayout as with the first level, add the new level's widgets to it, etc.

Qt is great in many ways, but I do think this problems shows that things could be a bit easier here.

风筝有风,海豚有海 2024-10-11 11:08:54

Qt帮助中的布局管理页面指出:

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

我的结论: Widget 需要手动销毁或通过销毁父 WIDGET,而不是布局来销毁

布局中的小部件是其上的小部件的子级
布局是安装的,而不是布局本身。 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;
    }
}

Layout management page in Qt's help states:

The layout will automatically reparent the widgets (using
QWidget::setParent()) so that they are children of the widget on which
the layout is installed.

My conclusion: Widgets need to be destroyed manually or by destroying the parent WIDGET, not layout

Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.

My conclusion: Same as above

To @Muelner for "contradiction" "The ownership of item is transferred to the layout, and it's the layout's responsibility to delete it." - this doesn't mean WIDGET, but ITEM that is reparented to the layout and will be deleted later by the layout. Widgets are still children of the widget the layout is installed on, and they need to be removed either manually or by deleting the whole parent widget.

If one really needs to remove all widgets and items from a layout, leaving it completely empty, he needs to make a recursive function like this:

// 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;
    }
}
难得心□动 2024-10-11 11:08:54

此代码删除其所有子项。因此布局内的所有内容都会“消失”。

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

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

下面是qDeleteAll的描述:

void qDeleteAll(ForwardIterator开始,ForwardIterator结束)

使用 C++ delete > 删除 [begin, end] 范围内的所有项目操作员。项目类型必须是指针类型(例如,QWidget *)。

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

This code deletes all its children. So everything inside the layout 'disappears'.

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

This deletes all direct widgets of the widget yourWidget. Using Qt::FindDirectChildrenOnly is essential as it prevents the deletion of widgets that are children of widgets that are also in the list and probably already deleted by the loop inside qDeleteAll.

Here is the description of qDeleteAll:

void qDeleteAll(ForwardIterator begin, ForwardIterator end)

Deletes all the items in the range [begin, end] using the C++ delete > operator. The item type must be a pointer type (for example, QWidget *).

Note that qDeleteAll needs to be called with a container from that widget (not the layout). And note that qDeleteAll does NOT delete yourWidget - just its children.

花落人断肠 2024-10-11 11:08:54

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

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

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

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

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

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

Untried: Why not create a new layout, swap it with the old layout and delete the old layout? This should delete all items that were owned by the layout and leave the others.

Edit: After studying the comments to my answer, the documentation and the Qt sources I found a better solution:

If you still have Qt3 support enabled, you can use QLayout::deleteAllItems() which is basically the same as hint in the documentation for QLayout::takeAt:

The following code fragment shows a safe way to remove all items from a layout:

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

Edit: After further research it looks like both version above are equivalent: only sublayouts and widget without parents are deleted. Widgets with parent are treated in a special way. It looks like TeL's solution should work, you only should be careful not to delete any top-level widgets. Another way would be to use the widget hierarchy to delete widgets: Create a special widget without parent and create all your removeable widgets as child of this special widget. After cleaning the layout delete this special widget.

我为君王 2024-10-11 11:08:54

您还需要确保删除间隔符和非 QWidget 的内容。如果您确定布局中唯一的东西是 QWidget,那么前面的答案就很好。否则您应该这样做:

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);

You also want to make sure that you remove spacers and things that are not QWidgets. If you are sure that the only things in your layout are QWidgets, the previous answer is fine. Otherwise you should do this:

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

It is important to know how to do this because if the layout you want to clear is part of a bigger layout, you don't want to destroy the layout. You want to ensure that your layout maintains it's place and relation to the rest of your window.

You should also be careful, you're are creating a load of objects each time you call this method, and they are not being cleaned up. Firstly, you probably should create the QWidget and QScrollArea somewhere else, and keep a member variable pointing to them for reference. Then your code could look something like this:

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);
赠佳期 2024-10-11 11:08:54

您不会写到另一种方式,但您也可以只使用 QStackedWidget 并向其中添加两个视图,每个视图对应您需要的按钮排列。在这两个按钮之间切换就不是问题了,而且比处理动态创建的按钮的各种实例的风险要小得多

You do not write about going the other way, but you could also just use a QStackedWidget and add two views to this, one for each arrangement of buttons that you need. Flipping between the two of them is a non issue then and a lot less risk than juggling various instances of dynamically created buttons

傻比既视感 2024-10-11 11:08:54

现有的答案都不适用于我的应用程序。到目前为止,对达科·马克西莫维奇的修改似乎有效。就是这样:

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;
    }
}

至少对于我的小部件和布局的层次结构来说,有必要递归并显式删除小部件。

None of the existing answers worked in my application. A modification to Darko Maksimovic's appears to work, so far. Here it is:

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;
    }
}

It was necessary, at least with my hierarchy of widgets and layouts, to recurse and to delete widgets explicity.

╭⌒浅淡时光〆 2024-10-11 11:08:54

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

QLayoutItem* child;
while ((child = pclLayout->takeAt(0)) != 0)
{
    if (child->widget() != NULL)
    {
        delete (child->widget());
    }
    delete child;
}

only works for my buttonlist, if the widgets themeselves are deleted, too. otherwise the old buttons are still visible:

QLayoutItem* child;
while ((child = pclLayout->takeAt(0)) != 0)
{
    if (child->widget() != NULL)
    {
        delete (child->widget());
    }
    delete child;
}
一瞬间的火花 2024-10-11 11:08:54

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

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

I had a similar case where I have a QVBoxLayout containing dynamically created QHBoxLayout objects containing a number of QWidget instances. For some reason I couldn't get rid of the widgets either by deleting neither the top level QVBoxLayout or the individual QHBoxLayouts. The only solution I got to work was by going through the hierarchy and removing and deleting everything specifically:

while(!vbox->isEmpty()) {
    QLayout *hb = vbox->takeAt(0)->layout();
    while(!hb->isEmpty()) {
        QWidget *w = hb->takeAt(0)->widget();
        delete w;
    }
    delete hb;
}
吾家有女初长成 2024-10-11 11:08:54

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

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

If you want to remove all widgets, you could do something like this:

foreach (QObject *object, _layout->children()) {
  QWidget *widget = qobject_cast<QWidget*>(object);
  if (widget) {
    delete widget;
  }
}
玻璃人 2024-10-11 11:08:54

我对这个问题有一个可能的解决方案(请参阅 Qt - 清除 QWidget 布局内的所有小部件)。通过两个单独的步骤删除所有小部件和布局。

第 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 应该是干净的。

I have a possible solution for this problem (see Qt - Clear all widgets from inside a QWidget's layout). Delete all widgets and layouts in two seperate steps.

Step 1: Delete all widgets

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

Step 2: Delete all layouts

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

After step 2 your MYTOPWIDGET should be clean.

猛虎独行 2024-10-11 11:08:54

如果您在将小部件添加到布局以及将布局添加到其他布局时没有做任何有趣的事情,那么它们都应该在添加到其父小部件时重新设置父级。所有QObject都有一个deleteLater()槽,一旦控制权返回到事件循环,该槽就会导致QObject被删除。在此 Manor 中删除的小部件也会删除其子项。因此,您只需对树中最高的项目调用 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_ 中所示)来显示局部变量,有多种口味,但前面的 ___技术上是保留的。

If you don't do anything funny when adding widgets to layouts and layouts to other layouts they should all be reparented upon addition to their parent widget. All QObjects have a deleteLater() slot which will cause the QObject to be deleted as soon as control is returned to the event loop. Widgets deleted in this Manor also delete their children. Therefore you simply need to call deleteLater() on the highest item in the tree.

in hpp

QScrollArea * Scroll;

in 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);
}

also note that in your example the _layout is a local variable and not the same thing as the _layout in the header file (remove the QVBoxLayout* part). Also note that names beginning with _ are reserved for standard library implementers. I use trailing _ as in var_ to show a local variable, there are many tastes but preceding _ and __ are technically reserved.

浅黛梨妆こ 2024-10-11 11:08:54

PyQt5 中存在某种错误,如果您使用上述方法,布局中仍会显示未删除的项目。所以我只是删除布局并重新开始:

例如

   def run_selected_procedures(self):
      self.commandListLayout.deleteLater()
      self.commandListLayout = QVBoxLayout()
      widget = QWidget()
      widget.setLayout(self.commandListLayout)
      self.scrollArea.setWidget(widget)
      self.run_procedures.emit(self._procSelection)

There's some sort of bug in PyQt5 where if you employ the above methods, there remain undeleted items shown in the layout. So I just delete the layout and start over:

E.g.

   def run_selected_procedures(self):
      self.commandListLayout.deleteLater()
      self.commandListLayout = QVBoxLayout()
      widget = QWidget()
      widget.setLayout(self.commandListLayout)
      self.scrollArea.setWidget(widget)
      self.run_procedures.emit(self._procSelection)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文