0

I am using Qt5 (beginner) on Windows7.
In the main window of my app I want to display and remove some push-buttons.

widget = new ButtonWidget(ui->frame); // frame is a QScrollArea
connect(ui->addBtns,    SIGNAL(clicked()), widget, SLOT(addButtons()));
connect(ui->deleteBtns, SIGNAL(clicked()), widget, SLOT(deleteButtons()));

And the ButtonWidget class is here:

ButtonWidget::ButtonWidget(QWidget * parent) : QWidget(parent)
{
   //addButtons();
}

void ButtonWidget::addButtons()
{
   QStringList texts{"1\nok", "2\nok", "3\nok", "4\nok", "5\nok", "6\nok"};
   gridLayout = new QGridLayout;
   for(int i = 0; i < texts.size(); i++)
   {
      QPushButton * button = new QPushButton(texts[i]);
      gridLayout->addWidget(button, i / 5, i % 5);
   }
   setLayout(gridLayout);
}

// I'm not sure this method/function is ok... :(
void ButtonWidget::deleteButtons()
{
    QLayoutItem * child;
    while((child = gridLayout->takeAt(0)) != 0)
    {
        gridLayout->removeWidget(child->widget());
        delete child->widget();
        delete child;
    }
    delete gridLayout;
}

Problem is: when I click on add_buttons, I get all buttons displayed, but they are shrunk, tiny or something... :
enter image description here

OTOH... if I remove the comment from addButtons() call in the constructor (hence calling from within the constructor), the result is ok:
enter image description here

So, finally I have 2 questions:
1) How to fix the code to be able to add those buttons properly (when add_buttons is clicked)?
2) Is the deleteButtons() method ok?

סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
  • You may have to implement `sizeHint()` on your ButtonWidget class or perhaps `resize(sizeHint());` at the end of `addButtons` – drescherjm Apr 21 '16 at 14:53
  • I guess you'll probably right, yet being a qt/c++ beginner I don't know exactly how to do that... I'll dig into docs maybe I'll figure out or find something useful on this matter. – סטנלי גרונן Apr 21 '16 at 14:58
  • I think you can change the title of your question, as it is rather with removing items from a layout that you have problems. Add was working fine. Then I'll upmark your question. – Werner Erasmus Apr 22 '16 at 06:42
  • Hmmm, sort of speaking... Having those tiny push-buttons is far from "working" fine :) – סטנלי גרונן Apr 22 '16 at 07:48

2 Answers2

2

First, I would recommend to do

gridLayout = new QGridLayout(this);

in the constructor. You don't need to delete all the grid, create it again and set in as a layout when you can just remove its content (with delete button for example) and fill it again afterwards.

EDIT : comment of Werner Erasmus : No need to set the layout. The fact that it has a parent widget implies that it sets itself up.

The problem is that is you do not modify addButtons() you will substitute the previous buttons without knowing where they go.

Also, try to give the QPushButton a parent :

new QPushButton(texts[i],this);

For your second point : Removing widgets from QGridLayout

Community
  • 1
  • 1
ElevenJune
  • 423
  • 1
  • 4
  • 21
1

EDIT:

After some more testing (without looking at the source code, but suspecting that it must be safe to delete button, else things would be brittle), I've implemented removeButtons as follows:

void ButtonWidget::deleteButtons()
{
    while(myLayout->count())
    {
        delete myLayout->itemAt(0)->widget();
    }
}

This works, and it proves that deleting a widget also removes the widget from it's parent, and layouts associated with the parent (by slots hooked up to when a child widget is deleted). The above code confirms this, as count(), which refers to number of layout items, decrease to zero (and those layout items are managed by the Layout). takeAt(x) is never called, and doesn't need to be - simply delete the widgets (buttons). Wholla!

ORIGINAL ANSWER

As mentioned in my other post, you only need to delete the buttons (it is removed from its parent automatically). However, if a widget was added to a layout, the parent of that layout becomes the parent of the widget, and an associated QLayoutItem is created that is managed by the layout itself. To the delete the buttons, the safest way is to take all the layout items (ownership the taker's responsibility), delete each items associated widget, and the delete each item. I'll try and find relevant references apart from the sources...

The following code works:

//ButtonWidget.h
#include <QWidget>
#include <QScrollArea>
#include <QHBoxLayout>

class ButtonWidget : public QScrollArea
{
    Q_OBJECT

  public:
    ButtonWidget(QWidget *parent = 0);
    ~ButtonWidget();
    void addButtons();
    void deleteButtons();
  private:
    QHBoxLayout* myLayout;
};

//ButtonWidget.cpp

#include "ButtonWidget.h"
#include <QGridLayout>
#include <QPushButton>
#include <QLayoutItem>

ButtonWidget::ButtonWidget(QWidget * parent) :
  QScrollArea(parent),
  myLayout(new QHBoxLayout(this))
{
}

ButtonWidget::~ButtonWidget()
{
}

void ButtonWidget::addButtons()
{
   QStringList texts{"1\nok", "2\nok", "3\nok", "4\nok", "5\nok", "6\nok"};

   for(int i = 0; i < texts.size(); i++)
   {
      myLayout->addWidget(new QPushButton(texts[i]));
   }
}

void ButtonWidget::deleteButtons()
{
    QLayoutItem * child;

    while((child = myLayout->takeAt(0)) != 0)
    {
      delete child->widget();
      delete child;
    }
}

#include "ButtonWidget.h"
#include <QApplication>
#include <QScrollArea>
#include <QPushButton>
#include <QGridLayout>
#include <QHBoxLayout>
#include <memory>

std::unique_ptr<QScrollArea> makeArea()
{
  std::unique_ptr<QScrollArea> area(new QScrollArea);
  auto layout = new QGridLayout(area.get());
  auto addButton  = new QPushButton("Add");
  auto removeButton  = new QPushButton("Remove");
  layout->addWidget(addButton, 0, 0);
  layout->addWidget(removeButton, 0, 1);
  auto btnWidget = new ButtonWidget;
  layout->addWidget(btnWidget,1,0,1,2);


  QObject::connect(addButton, &QPushButton::clicked, [=]()
  {
    btnWidget->addButtons();
  });

  QObject::connect(removeButton, &QPushButton::clicked, [=]()
  {
    btnWidget->deleteButtons();
  });
  return move(area);
}

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  auto area = makeArea();
  area->show();
  return a.exec();
}

You need to enable c++11 in your config (.pro) to get the lambdas working.

CONFIG += c++11

I've used QHBoxLayout for your buttons, as it better models what you want. Although strictly not necessary, I'm returning unique_ptr from the makeArea in main, as it has not parent, I'm not sure whether it gets some parent because it is the first widget created, but unique_ptr shows intent.

NOTE:

Apparently the layout item is not the parent of the widget, but the widget associated with the layout itself is the parent of widgets belonging to its layout item.

Werner Erasmus
  • 3,988
  • 17
  • 31
  • Thx very much for the wonderful effort! I'll surely give it a try tomorrow, first thing when I'll get to the office. C++11 is enabled and compile/build kit is msvc2012 or msvc2013, so definitely knows lambdas and such. Kind regards, SG. – סטנלי גרונן Apr 21 '16 at 19:58
  • takeAt already removes the layoutItem from the layout. I'm not sure what would happend if you call removeWidget after this, but I suspect somewhere a double delete happens in your deleteWidget. It crashed when I tested it – Werner Erasmus Apr 21 '16 at 20:05
  • @groenhen. Also be courteous if you are satisfied and tick my answers as "the accepted answer" (the other one too). – Werner Erasmus Apr 21 '16 at 20:08
  • I religiously do that. Don't worry about it, I am always polite: never down-voting (minor exceptions), always up-voting answers (minor exceptions), etc. Just give me the time to test it a little bit tomorrow, I'll tick "accepted answer" with infinite much pleasure than now voting blind. You have my word on that :) Kind regards, SG. – סטנלי גרונן Apr 21 '16 at 20:22
  • I made some progress with my approach and using ideas from here and there. It works but... I ran into some other kind of issues: http://stackoverflow.com/questions/36792633/qt-how-to-disable-the-shrink-to-fit-for-a-qgridlayout – סטנלי גרונן Apr 22 '16 at 11:31