【问题标题】:Clearing a Layout in Qt在 Qt 中清除布局
【发布时间】:2011-01-31 23:09:53
【问题描述】:

我正在 Qt 中创建一个应用程序,它允许用户在 QGraphicsView 中拖动各种“模块”。 Whenever one of these modules is selected, it emits a signal which is then picked up by a "ConfigurationWidget" which is a side-panel which should display all of the relevant parameters of the selected module:

class ConfigurationWidget : public QWidget
{
  Q_OBJECT

  public:
    ConfigurationWidget(QWidget *parent) : QWidget(parent) {}  

  public slots:
    void moduleSelected(Module* m)
    {
      if(layout())
      { 
        while (itsLayout->count()>0) 
        { 
          delete itsLayout->takeAt(0); 
        }
      }
      delete layout();

      itsLayout = new QFormLayout(this);
      itsLayout->addRow(QString(tr("Type:")),     new QLabel(m->name()));
      itsLayout->addRow(QString(tr("Instance:")), new QLabel(m->instanceID()));
      // ... Display a whole bunch of other fields that depends on the module
    }
};

The problem is that the ConfigurationWidget never actually gets cleared when a module is selected.新字段只是在旧字段上绘制。我尝试了 hide() 和 show()、invalidate()、update() 等的各种组合,但均无济于事。

制作一个可以像这样动态更改其字段的小部件的正确方法是什么?

【问题讨论】:

  • 你应该使用 QStackedWidget

标签: c++ qt qt4


【解决方案1】:

我之前使用的代码循环如下:

void clearLayout(QLayout *layout) {
    if (layout == NULL)
        return;
    QLayoutItem *item;
    while((item = layout->takeAt(0))) {
        if (item->layout()) {
            clearLayout(item->layout());
            delete item->layout();
        }
        if (item->widget()) {
           delete item->widget();
        }
        delete item;
    }
}

希望对你有所帮助!

【讨论】:

  • 我也会添加if(QSpacerItem *SpacerItem = Item->spacerItem()){ delete SpacerItem; } :)
  • 您不需要if (item->widget())。删除NULL 是一个nop。
  • @sjwarner 导致错误:item->spacerItem() 本质上是return dynamic_cast<QSpacerItem*>(item)。 IE。你会双重释放那块内存。注意:我只是凭经验知道这一点。
  • 这是所有答案中唯一有效的代码。但是,有一种更有效的方法不需要递归。首先删除所有小部件的直接子级,然后删除布局项而不递归。
  • ASAN 在 if (item->widget()) 处给我一个 heap-use-after-free 错误。
【解决方案2】:

如果您将布局转移到堆栈上分配的另一个小部件,则该布局中的小部件将成为新小部件的子级。当临时对象超出范围时,它会自动销毁布局和其中的所有小部件。

void moduleSelected(Module* m)
{
    if(layout())
        QWidget().setLayout(layout());

    itsLayout = new QFormLayout(this);
    itsLayout->addRow(QString(tr("Type:")),     new QLabel(m->name()));
    itsLayout->addRow(QString(tr("Instance:")), new QLabel(m->instanceID()));
    // ... Display a whole bunch of other fields that depends on the module
}

【讨论】:

  • 这非常丑陋,同时又非常聪明。 ;) 感谢分享这个技巧。我希望有QLayout::clear()...
【解决方案3】:

看起来最好的方法是使用 QStackedLayout,正如 armonge 所暗示的那样:

void ConfigurationWidget::moduleSelectedSlot(Module* m)
{
  QStackedLayout *stackedLayout = qobject_cast<QStackedLayout*>(layout());

  QWidget *layoutWidget = new QWidget(this);
  QFormLayout *formLayout = new QFormLayout(layoutWidget);
  formLayout->addRow(QString(tr("Type:")),     new QLabel(m->name()));
  formLayout->addRow(QString(tr("Instance:")), new QLabel(m->instanceID()));
  // ... Display a whole bunch of other fields that depends on the module

  delete stackedLayout->currentWidget();
  stackedLayout->addWidget(layoutWidget);
  stackedLayout->setCurrentWidget(layoutWidget);
}

【讨论】:

【解决方案4】:

我最近在使用 QFormLayout 时遇到了同样的问题。 对我有用的(也在 Qt 的文档中:http://qt-project.org/doc/qt-5/layout.html)是 takeAt(int) 方法。

void clearLayout(QLayout *layout)
{
     QLayoutItem *item;
     while ((item = layout->takeAt(0)))
         delete item;
}

【讨论】:

  • 小心:这不会删除小部件,只会将它们从布局中删除,因为布局项不拥有小部件。因此,如果您使用该代码多次重新创建 ui,该程序可能最终会占用所有内存。这不是真正的内存泄漏,因为子小部件会与父小部件一起被删除,但这可能具有相同的效果。
【解决方案5】:

使用while((item = layout-&gt;takeAt(0)))会导致警告消息“Invalid index take at 0”。 我改用count()

void clearLayout(QLayout *layout) 
{
    if (layout) {
        while(layout->count() > 0){
            QLayoutItem *item = layout->takeAt(0);
            QWidget* widget = item->widget();
            if(widget)
                delete widget;
            delete item;
        }
    }
}

【讨论】:

  • 一个有效的布局项可以使用 item->widget() 返回一个空指针,例如子布局或间隔。在这些情况下,此代码将崩溃。
  • @galinette 这取决于,但是,在一般情况下,在 nulltpr 上调用 delete 是不明智的。 stackoverflow.com/questions/6731331/…
  • 我建议不要使用“删除小部件”,而是使用小部件->deleteLater()。在大多数情况下,很难注意到差异,但可能存在差异。正如有关 QObject 的文档所暗示的那样:“当控制返回事件循环时,该对象将被删除”。这是重点。小部件可能会发送一些信号,并且在发送信号后,在同一个小部件实例中会进行一些其他调用。如果您以“原始方式”删除小部件,那么您将超出范围,在这种情况下,您在发送信号后将无法处理任何事情。
【解决方案6】:

当您希望项目从窗口中消失时,将它们重新设置为 NULL。我最近遇到了类似的问题,重新养育孩子解决了这些问题。

【讨论】:

  • 欢迎来到 Stack Overflow。这个问题已经有几个答案以及一个已被接受的答案。请说明您的答案与已经提供的解决方案有何不同或更好。
  • "reparenting to NULL" 对我来说非常有意义。这是一种使对象不可见但不删除它们的方法。它们仍然可以使用,并且可以稍后添加回图层中。 [您可以使用它来制作您自己版本的 QStackedWidget 类。 QStackedWidget 类在简单的情况下运行良好,但有其自身的怪异和特殊性,难以调整其大小。]
【解决方案7】:

到目前为止,我还没有找到其他答案。他们要么崩溃,要么没有完全清除布局。所以这就是我发现的工作,我希望其他人觉得这很有用。

void eraseLayout(QLayout * layout)
{
    while(layout->count() > 0)
    {
        QLayoutItem *item = layout->takeAt(0);

        QWidget* widget = item->widget();
        if(widget)
        {
            delete widget;
        }
        else
        {
            QLayout * layout = item->layout();
            if (layout)
            {
                eraseLayout(layout);
            }
            else
            {
                QSpacerItem * si = item->spacerItem();
                if (si)
                {
                    delete si;
                }
            }
        }
        delete item;
    }
}

【讨论】:

    【解决方案8】:

    感谢Vaidotas Strazdas's commentary,我找到了正确重置布局的解决方案。使用deleteLater()方法效果很好,否则delete关键字会导致刷新布局出现大量显示错误。

    void resetLayout(QLayout* apLayout)
    {
        QLayoutItem *vpItem;
        while ((vpItem = apLayout->takeAt(0)) != 0)  {
            if (vpItem->layout()) {
                resetLayout(vpItem->layout());
                vpItem->layout()->deleteLater();
            }
            if (vpItem->widget()) {
                vpItem->widget()->deleteLater();
            }
            delete vpItem;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 2023-03-24
      • 2016-04-20
      • 1970-01-01
      • 2013-02-18
      • 1970-01-01
      相关资源
      最近更新 更多