简体   繁体   English

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

[英]Qt - remove all widgets from layout?

This doesn't seem easy.这似乎并不容易。 Basically, I add QPushButton s through a function to a layout, and when the function executes, I want to clear the layout first (removing all QPushButton s and whatever else is in there), because more buttons just get appended to the scrollview .基本上,我通过 function 添加QPushButton到布局,当 function 执行时,我想先清除布局(删除所有QPushButton和其中的任何其他内容),因为更多按钮只是附加到scrollview

header header

QVBoxLayout* _layout;

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

}

I had the same problem: I have a game app whose main window class inherits QMainWindow.我有同样的问题:我有一个游戏应用程序,它的主窗口类继承了 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).当我想显示游戏的一个关卡时,我会实例化一个QGridLayout,在其中添加QLabels,然后将它们的像素图设置为某些图片(具有透明部分的像素图)。 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. (a) 我尝试删除 QGridLayout 并实例化一个新的,但后来了解到删除布局不会删除添加到其中的小部件。 (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. (b) 我尝试在新的像素图上调用 QLabel::clear() ,但这当然只对新像素有影响,对僵尸像素没有影响。 (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. (c) 我什至尝试删除我的 m_view 和 m_scene,并在每次显示新关卡时重建它们,但仍然没有运气。

Then (d) I tried one of the solutions given above, namely然后(d)我尝试了上面给出的解决方案之一,即

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() . (d) 中缺少的是调用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.然后我像第一个级别一样实例化一个新的 QGridLayout,向其中添加新级别的小部件,等等。

Qt is great in many ways, but I do think this problems shows that things could be a bit easier here. Qt 在很多方面都很棒,但我确实认为这个问题表明这里的事情可能会更容易一些。

Layout management page in Qt's help states: Qt 帮助中的布局管理页面指​​出:

The layout will automatically reparent the widgets (using QWidget::setParent()) so that they are children of the widget on which the layout is installed.布局将自动重新设置小部件的父级(使用 QWidget::setParent()),以便它们是安装了布局的小部件的子级。

My conclusion: Widgets need to be destroyed manually or by destroying the parent WIDGET, not layout我的结论:小部件需要手动销毁或通过销毁父 WIDGET,而不是布局

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."致@Muelner 的“矛盾”“项目的所有权已转移到布局,布局有责任将其删除。” - this doesn't mean WIDGET, but ITEM that is reparented to the layout and will be deleted later by the layout. - 这并不意味着 WIDGET,而是重新指向布局的 ITEM,稍后将被布局删除。 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;
    }
}

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 .这将删除小部件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 .使用Qt::FindDirectChildrenOnly必不可少的,因为它可以防止删除小部件的子部件,这些小部件也在列表中并且可能已经被qDeleteAll的循环删除。

Here is the description of qDeleteAll :这是qDeleteAll的描述:

void qDeleteAll(ForwardIterator begin, ForwardIterator end) void qDeleteAll(ForwardIterator 开始,ForwardIterator 结束)

Deletes all the items in the range [begin, end] using the C++ delete > operator.使用 C++ 删除 > 运算符删除 [begin, end] 范围内的所有项目。 The item type must be a pointer type (for example, QWidget *).项目类型必须是指针类型(例如,QWidget *)。

Note that qDeleteAll needs to be called with a container from that widget (not the layout).请注意,需要使用来自该小部件(而不是布局)的容器调用qDeleteAll And note that qDeleteAll does NOT delete yourWidget - just its children.并注意qDeleteAll不会删除yourWidget - 只是它的孩子。

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:编辑:在研究了对我的答案、文档和 Qt 源的评论后,我找到了一个更好的解决方案:

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:如果您仍然启用了 Qt3 支持,则可以使用 QLayout::deleteAllItems(),它与 ​​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.看起来 TeL 的解决方案应该有效,您只应该注意不要删除任何顶级小部件。 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.清理布局后删除这个特殊的小部件。

You also want to make sure that you remove spacers and things that are not QWidgets.您还需要确保删除间隔器和不是 QWidget 的东西。 If you are sure that the only things in your layout are QWidgets, the previous answer is fine.如果您确定布局中唯一的东西是 QWidgets,那么前面的答案就可以了。 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.首先,您可能应该在其他地方创建 QWidget 和 QScrollArea,并保留一个指向它们的成员变量以供参考。 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);

None of the existing answers worked in my application.现有的答案都不适用于我的应用程序。 A modification to Darko Maksimovic's appears to work, so far.到目前为止,对 Darko Maksimovic 的修改似乎奏效了。 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.至少在我的小部件和布局层次结构中,有必要明确地递归和删除小部件。

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

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.您不会写相反的事情,但您也可以只使用QStackedWidget并为此添加两个视图,一个用于您需要的每种按钮排列。 Flipping between the two of them is a non issue then and a lot less risk than juggling various instances of dynamically created buttons在这两者之间切换是没有问题的,而且比处理各种动态创建的按钮实例的风险要小得多

I had a similar case where I have a QVBoxLayout containing dynamically created QHBoxLayout objects containing a number of QWidget instances.我有一个类似的情况,我有一个QVBoxLayout包含动态创建的QHBoxLayout对象,其中包含许多QWidget实例。 For some reason I couldn't get rid of the widgets either by deleting neither the top level QVBoxLayout or the individual QHBoxLayouts.出于某种原因,我无法通过删除顶层 QVBoxLayout 或单个 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;
}

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 QObject s have a deleteLater() slot which will cause the QObject to be deleted as soon as control is returned to the event loop.所有QObject都有一个deleteLater()槽,一旦控制权返回到事件循环,它就会导致QObject被删除。 Widgets deleted in this Manor also delete their children.在此庄园中删除的小部件也会删除其子部件。 Therefore you simply need to call deleteLater() on the highest item in the tree.因此,您只需要在树中的最高项上调用deleteLater()

in hpp马力

QScrollArea * Scroll;

in cpp在 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).还注意到,在您的示例_layout是一个局部变量,而不是同样的事情_layout在头文件(删除QVBoxLayout*部分)。 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.我在var_使用尾随_来显示局部变量,有很多口味,但前面的___在技​​术上是保留的。

I have a possible solution for this problem (see Qt - Clear all widgets from inside a QWidget's layout ).我对此问题有一个可能的解决方案(请参阅Qt - 从 QWidget 的布局中清除所有小部件)。 Delete all widgets and layouts in two seperate steps.在两个单独的步骤中删除所有小部件和布局。

Step 1: Delete all widgets第 1 步:删除所有小部件

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

Step 2: Delete all layouts第 2 步:删除所有布局

    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.在第 2 步之后,您的 MYTOPWIDGET 应该是干净的。

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

There's some sort of bug in PyQt5 where if you employ the above methods, there remain undeleted items shown in the layout. PyQt5 中存在某种错误,如果您使用上述方法,布局中会显示未删除的项目。 So I just delete the layout and start over:所以我只是删除布局并重新开始:

Eg例如

   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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM