vbox.addWidget(widget) does not show widget - c++

Before my changes, I had several single widgets derived from a QWidget:
Wid* a = new Wid;
Wid* b = new Wid;
a->show();
b->show();
They were shown as expected.
But now I want to show them inside of a third widget, in one of its layouts.
Container* container = new Container(); // custom QWidget
There I say:
container->addLabel(a);
where I add the widget to a vertical layout vBox inside the Container:
void Container::addLabel(Label* lab){
this->labelList.append(lab);
vBox.addWidget(lab);
}
The problem: The widget a suddenly is hidden and does not show inside the Container. For testing, I tried addWidget(new QLabel("Check")); that worked. I don't see, what I could be doing wrong here...
EDIT1:
In the Container, this is how I set up the layout:
this->setLayout(&mainbox_);
mainbox_.addLayout(&buttonBox_);
mainbox_.addLayout(&vbox_);
// Test if the layout works: The QLabel is shown correctly.
QLabel* check = new QLabel;
check->setText("Dieser Text ist im Layout 'vbox_'.");
vbox_.addWidget(check);
Before adding a, it has a size of QSize(650, 279).
After the adding, it has a size of QSize(0, 0), even if I call show().
EDIT2:
Solved. It had to do with the size constraints in the ui-file. I changed the constraint of the top-level widget from fixed to preferred and voilĂ .

Related

QScrollArea messing up with QGridLayout : QGridLayout hidden and no scroll

I am trying to make a QGridLayout scrollable. It may contain several custom widgets, the number of widgets is not fixed. QGridLayout must be scrollable when there are more than x widgets, x being an arbitrary number.
The problem is, when I use QScrollArea, QScrollArea seems to hide the whole layout (only the background color of scroll area is shown). When I use QGridLayout alone, my view is - of course - not scrollable but everything works as it should do.
I am probably missing something, my guesses are:
I must fix somehow the size of the scrollable area, but I am not sure it's necessary or if I already have done it unconsciously
Parent Widgets or Child Widgets of the existing QScrollArea prevent QScrollArea by an unusual implementation to act normally.
Here's a piece of the involved code :
QScrollArea *scrollArea = new QScrollArea;
QWidget *resultsPage = new QWidget;
booksGrid = new QGridLayout;
booksGrid->setSizeConstraint(QLayout::SetMinAndMaxSize);
resultsPage->setLayout(booksGrid);
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(resultsPage);
mainWidget->addWidget(scrollArea);
Also, booksGrid is declared as a class attribute, mainWidget is a QStackedWidget.
Any help is welcomed, let me know if you need more information !
As I couldn't see anything suspicious in your code fragment, I made an MCVE to reproduce your issue:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QMainWindow qWin;
QScrollArea qScrArea;
QWidget qScrView;
QGridLayout qGrid;
enum { nCols = 4 };
#define MAKE_LABEL(I) \
QLabel qLbl##I(QString::fromUtf8("Label "#I)); \
qGrid.addWidget(&qLbl##I, I / nCols, I % nCols)
MAKE_LABEL(0); MAKE_LABEL(1); MAKE_LABEL(2); MAKE_LABEL(3); MAKE_LABEL(4);
MAKE_LABEL(5); MAKE_LABEL(6); MAKE_LABEL(7); MAKE_LABEL(8); MAKE_LABEL(9);
MAKE_LABEL(10); MAKE_LABEL(11); MAKE_LABEL(12); MAKE_LABEL(13); MAKE_LABEL(14);
MAKE_LABEL(15); MAKE_LABEL(16); MAKE_LABEL(17); MAKE_LABEL(18); MAKE_LABEL(19);
MAKE_LABEL(20); MAKE_LABEL(21); MAKE_LABEL(22); MAKE_LABEL(23); MAKE_LABEL(24);
MAKE_LABEL(25); MAKE_LABEL(26); MAKE_LABEL(27); MAKE_LABEL(28); MAKE_LABEL(29);
MAKE_LABEL(30); MAKE_LABEL(31); MAKE_LABEL(32); MAKE_LABEL(33); MAKE_LABEL(34);
MAKE_LABEL(35); MAKE_LABEL(36); MAKE_LABEL(37); MAKE_LABEL(38); MAKE_LABEL(39);
#undef MAKE_LABEL
qScrView.setLayout(&qGrid);
qScrArea.setWidget(&qScrView);
qWin.setCentralWidget(&qScrArea);
qWin.show();
// run-time loop
return app.exec();
}
Compiled and tested in VS2013, Qt 5.9.2 on Windows 10 (64 bit):
a)
b)
c)
The snapshots are taken after start (a), after resizing (b), and after scrolling (c).
For me, everything looks and works like expected.
You may compile and test the sample on your side as well. If it shows the same broken behavior like your application then something is wrong in your Qt version (otherwise something in your application).
The solution - resultsPage was a personalized widget containing several other widgets, with unspecified sizes, arranged in a QGridLayout.
Widgets were encapsulated like this :
QMainWidget -> QScrollArea -> personalized QWidget resultsPage -> QGridLayout -> personalized QWidgets result(s) with unspecified size
In the end the only thing I had to do was to set a fixed size in the constructor of the QWidget result with
setFixedSize(int w, int h);

Do Layouts become your children when you use setLayout in Qt

Here's the code:
class MyWidget: public QWidget
{
public:
MyWidget();
~MyWidget();
private:
QHBoxLayout* theLayout;
QVBoxLayout* subLayout1;
QVBoxLayout* subLayout2;
//More subLayouts
}
MyWidget::MyWidget()
{
theLayout = new QHBoxLayout();
subLayout1 = new QVBoxLayout();
subLayout2 = new QVBoxLayout();
//More subLayouts
//-------- Fill subLayouts with widgets using addWidget --------
theLayout->addLayout(subLayout1);
theLayout->addLayout(subLayout2);
//add all subLayouts
setLayout(theLayout);
}
MyWidget::~MyWidget()
{
//Destructor with nothing in it
}
**Note: the layouts are members of the class.
So I know that the widgets filled in the subLayouts are now MyWidget's children (or at least that's what I thought if not please tell me) so I don't need to delete them in the destructor (Qt cleans them up right?), but are the layouts also children of MyWidget or do I need to delete these in the destructor?
As the documentation says the setLayout call will reparent the given layout. So this widget will be it's parent, so you don't need to manually delete it.
I think you should use constructors with the argument if possible. And also you don't need to store the layouts as a members if you just initialize them.

QGraphicsView size in a GridLayout

I have found this: Getting the size of a QGraphicsView
But I can't figure out what does it mean to "move my initialization code to showEvent" and I can't comment on that answer.
I am want to resize a QPixmap so it could fit my QGraphicsView. I've placed my graphicsview in Designer and set GridLayout for my main window. In a MainWindow constructor I have written the following code:
ui->setupUi(this);
// Get GView size
g_sizeX = ui->mapView->width();
g_sizeY = ui->mapView->height();
// Init scene
scene = new QGraphicsScene(this);
// Init MAP pixmap and add it to scene
mapImage = new QPixmap(":/Map/europe.jpg");
QPixmap newmapImage = mapImage->scaled(g_sizeX, g_sizeY);
scene->addPixmap(newmapImage);
// Display scene in gview.
ui->mapView->setScene(scene);
But I always get size of 100x30. If I break the gridLayout, I get the correct size.
So, how should I deal with this?
Thank you.
The QGraphicsView will be resized by the QGridLayout after the widget is shown, and can be also resized later when the window is itself resized.
So you should change the size of the pixmap as a result of a QResizeEvent, either by subclassing QGraphicsView to redefine resizeEvent(), and then promoting your view object to your new class in the designer to use it instead of QGraphicsView, or by installing your MainWindow object as an event filter for the view to handle to the resize event from the MainWindow::eventFilter function.
You probably don't want to change the pixmap size in the scene, but rather adjust the view matrix so that your QGraphicsPixmapItem fits perfectly inside the view, with QGraphicsView::fitInView.
For example:
/* QGraphicsPixmapItem *pixmapItem; as a MainWindow member */
pixmapItem = scene->addPixmap(newmapImage);
/* Either always disable or enable the scrollbars (see fitInView doc) */
ui->mapView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->mapView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->mapView->installEventFilter(this);
...
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
if(obj == ui->mapView && evt->type() == QEvent::Resize) {
ui->mapView->fitInView(pixmapItem, Qt::KeepAspectRatioByExpanding);
}
// Call the base class implementation
return QMainWindow::eventFilter(obj, evt);
}
I believe that what is happening is that Qt only applies layouts and sets widget sizes when the widget is first displayed.
One way to work that is to override QWidget::showEvent(), and put your sizing code in there.
However, one simpler way, that often works in constructors, is to ask the widget for its sizeHint(), rather than for its not-yet-layed-out size.
In your case, that would mean changing two lines of code to:
g_sizeX = ui->mapView->sizeHint().width();
g_sizeY = ui->mapView->sizeHint().height();
If your layout isn't too complicated, and if you haven't overridden the default size policies, this may well fix things for you.

Qt - remove all widgets from layout?

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

Getting Parent Layout in Qt

quick question. Is there any way to (easily) retrieve the parent layout of a widget in Qt?
PS: QObject::parent() won't work, for logical reasons.
EDIT:
I'm positive the widget has a parent layout, because I added it to a layout earlier in the code. Now, I have many other layouts in the window and while it is possible for me to keep track of them, I just want to know if there is an easy and clean way to get the parent layout.
EDIT2:
Sorry, "easy and clean" was probably not the best way of putting. I meant using the Qt API.
EDIT3:
I'm adding the widget to the layout like this:
QHBoxLayout* layout = new QHBoxLayout;
layout->addWidget(button);
SOLVED!
Usage: QLayout* parentLayout = findParentLayout(addedWidget)
QLayout* findParentLayout(QWidget* w, QLayout* topLevelLayout)
{
for (QObject* qo: topLevelLayout->children())
{
QLayout* layout = qobject_cast<QLayout*>(qo);
if (layout != nullptr)
{
if (layout->indexOf(w) > -1)
return layout;
else if (!layout->children().isEmpty())
{
layout = findParentLayout(w, layout);
if (layout != nullptr)
return layout;
}
}
}
return nullptr;
}
QLayout* findParentLayout(QWidget* w)
{
if (w->parentWidget() != nullptr)
if (w->parentWidget()->layout() != nullptr)
return findParentLayout(w, w->parentWidget()->layout());
return nullptr;
}
(Updated answer)
I guess it is not easily possible then. Since a Widget can be technically contained in multiple layouts (a horizontal layout which is aligned inside a vertical layout, for instance).
Just remember that a QWidget's parent does not change if it is aligned in a layout.
You possibly have to keep track of that yourself, then.
Simply use:
QHBoxLayout* parentLayout = button->parentWidget()->layout();
I assume button is a child of the widget which contains the layout which contains button. button->parentWidget() returns a pointer to the widget of the button's parent and ->layout() returns the pointer to the layout of the parent.
After some exploration, I found a "partial" solution to the problem.
If you are creating the layout and managing a widget with it, it is possible to retrieve this layout later in the code by using Qt's dynamic properties. Now, to use QWidget::setProperty(), the object you are going to store needs to be a registered meta type. A pointer to QHBoxLayout is not a registered meta type, but there are two workarounds. The simplest workaround is to register the object by adding this anywhere in your code:
Q_DECLARE_METATYPE(QHBoxLayout*)
The second workaround is to wrap the object:
struct Layout {
QHBoxLayout* layout;
};
Q_DECLARE_METATYPE(Layout)
Once the object is a registered meta type, you can save it this way:
QHBoxLayout* layout = new QHBoxLayout;
QWidget* widget = new QWidget;
widget->setProperty("managingLayout", QVariant::fromValue(layout));
layout->addWidget(widget);
Or this way if you used the second workaround:
QHBoxLayout* layout = new QHBoxLayout;
QWidget* widget = new QWidget;
Layout l;
l.layout = layout;
widget->setProperty("managingLayout", QVariant::fromValue(l));
layout->addWidget(widget);
Later when you need to retrieve the layout, you can retrieve it this way:
QHBoxLayout* layout = widget->property("managingLayout").value<QHBoxLayout*>();
Or like this:
Layout l = widget->property("managingLayout").value<Layout>();
QHBoxLayout* layout = l.layout;
This approach is applicable only when you created the layout. If you did not create the layout and set it, then there is not a simple way of retrieving it later. Also you will have to keep track of the layout and update the managingLayout property when necessary.
use widget.parent().layout() and search brute force (recursion included) is my only advice. Maybe you can search be "name".
Have you tried this? Don't forget to check for NULL.
QLayout *parent_layout = qobject_cast< QLayout* >( parent() );
If parent_layout equals NULL, then the parent widget is not a layout.
Have you tried QWidget::layout() ?