Solved Widget Handling
-
Hi Gurus!
I am a newbie to QT, but have been developing in C++ (MFC) on Visual Studio for more years than I care to think.
I have a couple of basic questions regarding widgets that I can't seem to find answers to.
My app is a QT Widget app. On running my program, there are no widgets, it is just an empty window. The first thing my software does is add a widget (my centralWidget), and attach a QHBoxLayout to it. I then scan my network for uPnp devices, and for each device that is found I dynamically create a QVBoxLayout, and attach to it a QAbstractButton (QLedIndicator) and a QLabel. This is then attached to the QHBoxLayout mentioned earlier. The dynamically created QVBoxLayout pointers are stored in a QVarLengthArray (m_buttonArray) so I can access then at a later date.
Here is my widget creation:
// Create a new LED button QVBoxLayout *vlay = new QVBoxLayout(); vlay->setAlignment(Qt::AlignCenter); QLedIndicator *btn = new QLedIndicator( m_centralWidget ); vlay->addWidget( btn ); QString temp = ws->friendlyName.left(4); QLabel *label = new QLabel( temp, this); vlay->addWidget( label ); // Add the new button to the window m_hBoxLayout->insertLayout( 0, vlay ); connect( btn, SIGNAL(clicked(bool)), this, SLOT(switchPressed(bool))); m_buttonArray.append( vlay ); // Store the pointer to my vLayout
A question:
- Is this correct "new QLedIndicator( m_centralWidget );" or should it be "new QLedIndicator( this );" or something else?
One of the things I do is allow the user to re-scan the network for new devices. Before I do this I delete all the window content that was previously created.
Here is my code to do this:
foreach( QVBoxLayout *vlay, m_buttonArray ) { // Delete the widgets QWidget *pw = vlay->parentWidget(); if (pw != NULL) { foreach ( QWidget *w, pw->findChildren<QWidget*>() ) { if ( vlay->indexOf( w ) >= 0 ) { w->close(); delete w; } } else // do something m_hBoxLayout->removeItem( vlay ); delete vlay; } } m_buttonArray.clear();
My questions are as follows:
-
Is this the correct way to delete my widgets and layout?
-
Does "close"ing the widget free it, or should I do that with delete (they were created with new)?
-
I expected the main window to resize back to a small window, but it doesn't change size at all, how to I tell the main window to resize smaller now that these widgets have been deleted?
With the exception of the window not resizing, this code works, but I want to make sure I have no memory leaks.
Thanks heaps in advance for your help!
Steve Q. :-)
-
Hi, welcome to the forum.
Is this correct "new QLedIndicator( m_centralWidget );" or should it be "new QLedIndicator( this );" or something else?
It doesn't matter. You might as well do
new QLedIndicator()
. This is because in the next line you add that widget to a layout. Adding widget to a layout changes its parent to be the widget that is governed by that layout. Thebtn
object will be destroyed when the widget of the layout is.Is this the correct way to delete my widgets and layout?
You don't need any of that. To delete a widget, its layout and all the widgets inside it all you have to do is delete the top most widget, so
delete m_centralWidget
will delete that widget and everything in it.Does "close"ing the widget free it, or should I do that with delete (they were created with new)?
It depends. By default no. The widget will be deleted by its parent (if it has any). If you want it to delete automatically when it's closed you need to set an attribute:
widget->setAttribute(Qt::WA_DeleteOnClose)
. Just be careful not to set it on stack allocated widgets or it will "double delete" and crash.I expected the main window to resize back to a small window, but it doesn't change size at all, how to I tell the main window to resize smaller now that these widgets have been deleted?
Widgets grow but don't shrink by default. To shrink call
adjustSize()
. It will shrink to the size returned fromsizeHint()
. -
Hey Chris,
Thanks, it's good to be here!
Thank you for your answer (and patience) to my questions.
Since posting my question, I have added 3 buttons to my main window that are permanent and will not be deleted. Consequently deleting m_centralWidget is not an option for me. So I guess given this, my question still stands. Am I doing it correctly? I will as you suggest though, set the DeleteOnClose attribute.
I have tried adjustSize() and nothing happens? Could this be because the HBoxLayout is still there, and that is restricting the window size?
Thanks again so much for your help. A lot more is clearer now.
Steve Q. :-)
-
So I guess given this, my question still stands. Am I doing it correctly?
Well it does what it's suppose to, but it's kinda verbose. It's also not very fast, as it does a lot of searches (
findChildren()
is recursive andindexOf()
is another find).If it's an option you could put all the "removable" stuff in another "container" widget and remove it in one call.
Alternatively you could put all the layouts in one container (like you do now), the widgets i another and then do:qDeleteAll(m_WidgetsArray); m_WidgetsArray.clear(); qDeleteAll(m_LayoutsArray); m_LayoutsArray.clear();
As for why
adjustSize()
doesn't work it's hard to say without seeing the whole thing. It should resize tosizeHint()
of the window widget, so you can check what that returns after removing widgets and work your way backwards from that - by default and if you haven't messed with size policies too much thesizeHint()
is the sum of the minimal sizes of the contents + paddings and margins. -
Thanks again Chris,
I considered early on storing pointers for faster retrieval, so I may revert to plan A!
Thanks again for your help.
Steve Q. :-)
-
Hey Chris,
Sorry but I do have one more question related to my post.
With regards to the window sizeHint() I checked the size of both the window and horizontal layout after I had added the constant buttons. It was 95 x 87 for both. After I added the buttons and then removed them, the hBoxLayout went back to 95 x 87, but the window remained at 580 x 87. This is obviously why the adjustSize() won't work.
In case I am setting up the window incorrectly, I create the default buttons and set up the window like this:ui->setupUi(this); delete ui->mainToolBar; this->setWindowFlags( Qt::FramelessWindowHint ); // Create the main horizontal layout and central widget m_centralWidget = new QWidget( this ); m_hBoxLayout = new QHBoxLayout( m_centralWidget ); m_hBoxLayout->setAlignment(Qt::AlignCenter); // Create a vertical layout for the close button QVBoxLayout *vlay = new QVBoxLayout(); vlay->setAlignment(Qt::AlignRight | Qt::AlignTop); // Load the close button image QPixmap pixmap( ":/Images/Error.ico" ); pixmap.scaled( QSize( 10, 10 ) ); //Turn it into an icon QIcon ButtonIcon( pixmap ); // Create the close button and add the icon QPushButton *close = new QPushButton; close->setIcon(ButtonIcon); // Add the close button to the vertical layout vlay->addWidget( close ); // Create a vertical layout for the rescan and settings button QVBoxLayout *vlay2 = new QVBoxLayout(); vlay2->setAlignment(Qt::AlignRight | Qt::AlignTop); // Load the rescan button image QPixmap pixmap2( ":/Images/Refresh.ico" ); pixmap2.scaled( QSize( 10, 10 ) ); //Turn it into an icon QIcon ButtonIcon2( pixmap2 ); // Create the close button and add the icon QPushButton *rescan = new QPushButton; rescan->setIcon(ButtonIcon2); // Add the close button to the vertical layout vlay2->addWidget( rescan ); // Load the settings button image QPixmap pixmap3( ":/Images/Settings.ico" ); pixmap3.scaled( QSize( 10, 10 ) ); //Turn it into an icon QIcon ButtonIcon3( pixmap3 ); // Create the close button and add the icon QPushButton *settings = new QPushButton; settings->setIcon(ButtonIcon3); // Add the close button to the vertical layout vlay2->addWidget( settings ); // Add the vertical laytou to the main horizontal layout m_hBoxLayout->addLayout( vlay2 ); m_hBoxLayout->addLayout( vlay ); // Connect the slot for when the close button is clicked connect( close, SIGNAL( clicked(bool) ), this, SLOT( close() ) ); // Connect the slot for when the close button is clicked connect( rescan, SIGNAL( clicked(bool) ), this, SLOT( rescanSwitches() ) ); // Connect the slot for when the close button is clicked connect( settings, SIGNAL( clicked(bool) ), this, SLOT( settings() ) ); setCentralWidget( m_centralWidget );
-
Does this look correct?
-
Can you suggest perhaps why my main window's size hint is not shrinking or how I can force it to match the hBoxLayout?
Thanks so much once again,
Steve Q. :-)
-
-
If you're calling
adjustSize()
right after the removal of the widgets and layouts the various resize and relayouting events were not processed yet. You need to wait until they are.There are couple of ways to do it. You could force event processing like this:
qApp->processEvents(); adjustSize();
or you can wait until they are processed automatically in the event loop:
//0 is a special value meaning "after all pending events are processed" QTimer::singleShot(0, this, &MainWindow::adjustSize);
On a side note. Since you said you're new to Qt here are some pointers:
- Instead of deleting the toolbar with
delete ui->mainToolBar;
don't create it in the first place. Open the form editor and remove it there. - Creating a QPixmap from an .ico file, then scaling it and copying to QIcon is very wasteful. You will loose image quality, take more time and memory. If you want fixed size icons I suggest you create a 10x10 .png image and use it like this:
new QPushButton(QIcon(":/Images/Error.png"), "close");
without all the conversions. - Consider switching to the functor based connections. They are faster, validated at compile time and you don't need to spell out param types:
connect(rescan, &QPushButton::clicked, this, &MainWindow::rescanSwitches);
- You can pass the window flags right in the constructor of the base class. This avoids doing all the windowing work twice:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent, Qt::FramelessWindowHint), ui(new Ui::MainWindow) ...
- Instead of deleting the toolbar with
-
Hey Chris,
Thanks so much for this info.
processEvents() made a difference, and although the window didn't shrink to the original size, it was close!
Thanks for the other tips too. I'll most definitely look at implementing them.
With respect to the 10x10 png suggestion, back in Visual Studio there were issues when my software was run on a system with a 4k monitor and a system with a 2k monitor. The difference in resolutions created issues with dialog layout and icon size (but only with icons and images, not widgets/controls). I wasn't sure if I would have the same issue here, so I have left my icons large, in case I have to modify the size of them based on the system's screen resolution. I haven't got that far yet!
Once again, I really appreciate your advice.
Cheers!
SQ :-)