What is parent for in Qt? - qt

Almost every QtWidgets class can have parent. And usually it's optional to set parent at object initialization. For example,If I create a class that inherits QWidget class, I will do the following on the constructor:
Widget::Widget(QWidget* parent): QWidget(parent) {
hbox = new QHBoxLayout(this);
yes_button = new QPushButton("&Yes");
no_button = new QPushButton("&No", this);
cancel_button = new QPushButton("&Cancel", hbox);
}
I can set or not set parent. I can set cancel_button to be a child of hbox. I can too set cancel_button to be a child of yes_button, but I think it's a bad thing to do.
What's the point of this? And, is it really necessary to set parent for every QWidget based class that I create?

Besides helping with draw order in GUI objects, it also helps with memory management, so that when you destroy a QObject, all of it's children are destroyed too. See http://doc.qt.io/qt-4.8/objecttrees.html for more details. When something changes in the parent (e.g. when it is resized), it can notify its children to update themselves too.
To answer your question, you're not required to set the parent for everything (that's why it's an optional parameter, after all), but most of the time it's better to set it correctly.

Firstly, a QWidget is a QObject, and QObjects are nodes in a QObject tree. The child nodes are memory-managed by the parent, unless you deallocate them before the parent has a chance to do so. Thus, memory management is one reason for widgets, or any other QObjects, to have a parent.
Secondly, visible parentless widgets are always top-level windows. Conversely, it's impossible to have a non-top-level widget that is parentless. When you show a parentless widget, it acquires its own window. The opposite is not necessarily true - it's possible to give a child widget a Qt::Window flag, and it becomes a top-level window as well.
The corollary is that any widget contained in other widgets has a parent - otherwise it'd be a top-level window. This parent might not be set explicitly by you, but it's set nevertheless.
I think that your question can be rephrased as: When do I need to explicitly give widget constructor a parent? The answer is:
Whenever the widget is a top level window that you intend to have a parent. Such windows are not subject to layout management, so there's no mechanism to set that parent for you. Top-level transient dialogs need to have parents so that they are properly positioned in relation to the parent window.
Whenever you have a child widget not subject to layout management.
Widgets subject to layout management are parented upon insertion into a layout:
int main(int argc, char ** argv) {
QApplication app(argc, argv);
QWidget window;
QVBoxLayout layout(&window);
QLabel label("Hello");
QPushButton button("Goodbye");
layout.addWidget(&label);
layout.addWidget(&button);
QObject::connect(&button, &QPushButton::clicked, [&app]{ app.quit(); });
window.show();
return app.exec();
}
Finally, not all widgets or QObjects need to be explicitly created on the heap. Since all QObject-derived classes in Qt (and many other classes, too!) use the PIMPL idiom, when you allocate them individually on the heap, you're really doing the heap allocation twice. First you allocate the instance of the class - sometimes the instance is as small as a pointer or two - and then the class's constructor allocates its PIMPL. Explicit heap allocation is a case of premature pessimization.
To avoid this pessimization, your Widget should look as follows:
class Widget : public QWidget {
Q_OBJECT
QHBoxLayout m_layout;
QPushButton m_yesButton, m_noButton, m_cancelButton;
public:
Widget(QWidget * parent = 0);
};
Widget::Widget(QWidget * parent) :
QWidget(parent),
m_layout(this),
m_yesButton("&Yes"),
m_noButton("&No"),
m_cancelButton("&Cancel")
{
m_layout.addWidget(&m_yesButton);
m_layout.addWidget(&m_noButton);
m_layout.addWidget(&m_cancelButton);
}
If you wished to use the PIMPL idiom, you could do that, too:
// Widget.h - Interface
class WidgetPrivate;
class Widget : public QWidget {
{
Q_OBJECT
Q_DECLARE_PRIVATE(Widget)
QScopedPointer<WidgetPrivate> const d_ptr;
public:
Widget(QWidget * parent = 0);
~Widget();
};
// Widget.cpp - Implementation
class WidgetPrivate {
Q_DISABLE_COPY(WidgetPrivate)
Q_DECLARE_PUBLIC(Widget)
Widget * const q_ptr;
QHBoxLayout layout;
QPushButton yesButton, noButton, cancelButton;
public:
WidgetPrivate(Widget * q);
};
WidgetPrivate::WidgetPrivate(Widget * q) {
q_ptr(q),
layout(q),
yesButton("&Yes"),
noButton("&No"),
cancelButton("&Cancel")
{
layout.addWidget(&yesButton);
layout.addWidget(&noButton);
layout.addWidget(&cancelButton);
}
Widget::Widget(QWidget * parent) :
QWidget(parent),
d_ptr(new WidgetPrivate(this))
{}
Widget::~Widget() {}
// necessary, since WidgetPrivate is unknown to the interface!
Of course, you should be using QDialogButtonBox instead of all this :)

Related

Promote custom widget with custom constructor in QT Creator

I know, this is basically the same question, but my problem goes further.
The following tree explains my structure:
QWidget
|
CustomWidget
| |
MyTable MyWidgetAroundIt
I have promoted MyTable in Qt Designer. So, I can add it to MyWidgetAroundIt. That worked quite well. The only problem is, CustomWidget requires it's parent to be a CustomWidget too, its constructor looks like:
CustomWidget(CustomWidget* parent) : QWidget(parent), _specialValue(parent->getSpecialValue)
This causes compile errors, as the designer generated code tries to initialize MyTable with a QWidget*, instead of the CustomWidget*. What could/should I do to prevent this and/or give the designer a hint about this requirement?
A widget whose parent can't be a QWidget is not a widget anymore. Your design breaks the Liskov Substitution Principle and has to be fixed.
You're free to enable special functionality if the widget happens to be of a certain type, but a widget must be usable with any widget for a parent.
Thus:
CustomWidget(QWidget* parent = nullptr) :
QWidget(parent)
{
auto customParent = qobject_cast<CustomWidget*>(parent);
if (customParent)
_specialValue = customParent->specialValue();
}
or:
class CustomWidget : public QWidget {
Q_OBJECT
CustomWidget *_customParent = qobject_cast<CustomWidget*>(parent());
SpecialType _specialValue = _customParent ? _customParent->specialValue() : SpecialType();
SpecialType specialValue() const { return _specialValue; }
public:
CustomWidget(QWidget * parent = nullptr) : QWidget(parent) {}
};

Can you hide a QGroupBox frame but preserve it's content visible?

I have a QGroupBox. Depending on the context, it's title may be redundent (displayed in another place of the GUI), so I then need to make as if the QGroupBox was not here....but I must preserve it's content visible (so I don't want to call QGroupBox::hide())!
I need to do this dynamically at runtime and would like to avoid creating/destroying the QGroupBox + reparenting it's content....there must be an easier way to do this.
What I tried so far:
QGroupBox visible:
QGroupBox::setTitle("") removes the text.
QGroupBox::setFlat(true) makes the frame be a single line.
I end up with this:
Not too bad...but a line remains....is there a way to completely hide the QGroupBox frame but preserve it's content visible?
My option:
QGroupBox theBox;
theBox.setFlat(true);
//This removes the border from a QGroupBox named "theBox".
theBox.setStyleSheet("QGroupBox#theBox {border:0;}");
//This removes the border from the group box and all of its children
theBox.setStyleSheet("border:0;");
You can derive your own Group Box from the QGroupBox and reimplement the paintEvent() method. It should be very simple. Original QGroupBox::paintEvent() looks like this:
void QGroupBox::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
What you need to do is just to modify the style option right before the widget is painted:
void CMyGroupBox::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
// This should disable frame painting.
option.features = QStyleOptionFrame::None;
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
You can use QFrame + QGridLayout (or some more complex combination of layouts) + QSS instead of a QGroupBox.
Considering a QGroupBox only, a trivial solution via QSS could be:
static const char kSavedTitle[] = "_savedTitle";
void hideBoxFrame(QGroupBox * box) {
box->setProperty(kSavedTitle, box->title());
box->setTitle(QString());
box->setStyleSheet("border:none");
}
void showBoxFrame(QGroupBox * box) {
box->setTitle(box->property(kSavedTitle).toString());
box->setStyleSheet(QString());
}
Here's an example that does it by swapping the widgets and reparenting the children. It works for any widget that has direct children, not only QGroupBox. It would require special case handling for widgets such as QScrollArea and QMainWindow that wrap children in a special sub-widget.
See this question for a related discussion of programmatically promoting widgets.
// https://github.com/KubaO/stackoverflown/tree/master/questions/group-reparent-36603051
#include <QtWidgets>
/// Replaces the visible widget with a hidden widget, preserving the layout of the
/// children, and making the new widget visible.
void swapWidgets(QWidget * a, QWidget * b)
{
auto src = a->isVisible() ? a : b;
auto dst = a->isVisible() ? b : a;
Q_ASSERT(dst->isHidden());
/// Move the children to the destination
dst->setLayout(src->layout());
/// Replace source with destination in the parent
auto layout = src->parentWidget()->layout();
delete layout->replaceWidget(src, dst);
/// Unparent the source, otherwise it won't be reinsertable into the parent.
src->setParent(nullptr);
/// Only the destination should be seen.
src->hide();
dst->show();
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QGridLayout wLayout{&w};
QPushButton swapBtn{"Swap"};
wLayout.addWidget(&swapBtn);
QWidget noBox;
QGroupBox box{"Group"};
wLayout.addWidget(&box);
QGridLayout boxLayout{&box};
for (int i = 0; i < 16; ++i)
boxLayout.addWidget(new QLabel(QString("Tr%1").arg(i)), i/8, i%8);
swapBtn.connect(&swapBtn, &QPushButton::clicked, [&] { swapWidgets(&box, &noBox); });
w.show();
return app.exec();
}
Yes there is a alternative that you can Try.
You can morph into a QFrame which will keep the behavior But make the container boundaryless
You can simply right click on the Group Box in the QDesigner and Select the 'Morph Into' option to select from

Can't use setWindowsFlag when set parent to Qdialog

I have QDialog which is a child of mainWindow. My problem is I can't use setWindowsFlag when set parent for the dialog: the dialog sticks to the top left of MainWindow and is transparent .The code look like this:
mainWindow::MainWindow(QWidget* parent): QMainWindow(parent)
{
this->setWindowsFlags(Qt::FramelessWindowHint);
mpConfirmDialog = new ConfirmDialog();//mpConfirmDialog is a pointer and member of MainWindow
mpConfirmDialog->setParent(ui->centralWidget) ;//pass ui->centralWidget in constructor cause crash when exit????
mpConfirmDialog->hide();
}
In constructor of ConfirmDialog:
ConfirmDialog::ConfirmDialog(QWidget* parent){
this->setWindowsFlags(Qt::FramelessWindowHint);// only effective if comment mpConfirmDialog->setParent(ui->centralWidget) in MainWindow
}
Any ideas are appreciated.
There is some inconsistency between OSes but we should keep in mind that the parent class constructor very likely sets some flags already and the flag add logic is bitwise OR. We can try to ensure we don't loose those flags.
this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint);
This may help to degree but of course I don't have your environment and entire code sample to prove.

QPushButton Main Gui

I'm beginning with Qt and I do not understand something. Here is my code:
#include <QApplication>
#include <QtWebKitWidgets/QWebView>
#include <QMainWindow>
#include <QPushButton>
QApplication a(argc, argv);
QMainWindow *mWin = new QMainWindow();
QPushButton *button = new QPushButton("Button");
button->setGeometry(0,0,128,56);
button->show();
mWin->setGeometry(QRect(300,300,1024,640));
mWin->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint);
mWin->show();
return a.exec();
When I launch the program, it makes a pushbutton, but not on the main GUI. It instead makes it in the upper lefthand corner of my scoeen. How do I make the pushbutton part of the main GUI?
Both QMainWindow and QPushButton derive from QWidget and generally, QWidgets can display themselves independently, so don't need to be attached to a parent, though it's likely you'll want to group and organise the widgets you're presenting to the user.
For a beginner, I think that QMainWindow is a bit misleading as you don't actually need it to display widgets. If, for example, you wanted to just display the button, you only need create the QPushButton and call show: -
QPushButton pButton = new QPushButton("Test");
pButton->show();
Though it's rare that you'll actually want to do this. In actuality, though it appears pointless, you can even just create a QWidget and display that: -
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.show();
return a.exec();
}
The QWidget can now be the parent to all other widgets, still without you defining a QMainWindow.
It is likely that you will want other widgets to display and Qt uses a parent / child hierarchy system of linking them together. Amongst other things, an advantage here is that you don't need to keep track of the different widgets in order to release their memory. When you're finished, you just delete the top level item, which may be a QMainWidget, or QDialog or other QWidget based object. In the case of Windows, all you need to do is close them and their children are cleaned up too.
Adding Widgets to a QMainWindow can be done in different ways, depending upon what you're trying to achieve. Eventually, you'll likely want to use layouts that allow grouping of widgets in logical arrangements, but to start with, if you look at the constructor of the QPushButton, you'll see that it takes an optional parameter of QWidget* : -
QPushButton(QWidget * parent = 0)
This allows you to attach the widget you're creating (QPushButton in this case) to a parent. Therefore, create the QMainWindow and then pass its pointer to the QPushButton.
QMainWindow* pWindow = new QMainWindow; // create the main window
QPushButton pButton = new QPushButton(pWindow); // create the push button
pWindow->show(); // Display the main window and its children.
You're creating a button and a window, but you're not associating them together. Therefore, the button is not part of the main UI. In order to add the button to the main window, you should add the following line-
mWin->setCentralWidget(button);
When creating user interfaces with Qt, it's better to avoid setting the geometry explicitly. Here is a nice page which describes the layouting in Qt.
Also, you seem to be missing a main function.
You are not setting parent child relationship, you need QPushbutton as a child of QMainwindow. Then you can see QPushbutton on QMainwindow.
Please try this, I am not sure what exactly you want to achive..but below code will give some hind about how to proceed..
QApplication a(argc, argv);
QMainWindow *mWin = new QMainWindow();
QPushButton *button = new QPushButton(mWin); // setting parent as QMainwindow
button->setGeometry(0,0,128,56);
// button->show();
//mWin->setCentralWidget( button );
mWin->setGeometry(QRect(300,300,1024,640));
mWin->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint);
mWin->show()
;
Create a central widget for the main window and then use a layout to place the push button on the central widget. For example:
QMainWindow *mWin = new QMainWindow();
// Place the button in a layout
QHBoxLayout *layout = new QHBoxLayout;
QPushButton *button = new QPushButton("Button");
layout->addWidget(button);
// Set up a central widget for the main window
QWidget *centralWidget = new QWidget();
centralWidget->setLayout(layout);
mWin->setCentralWidget(centralWidget);
If you've placed your button into a layout this way (or as a central widget itself, as one of the other answers has indicated), you don't need to set the geometry on the button, nor do you need to call setVisible or show on the button explicitly (but you still need to on the main window).
Consider looking through some of Qt's examples, such as the application example for more information.

How to make a Qt widget invisible without changing the position of the other Qt widgets?

I've got a window full of QPushButtons and QLabels and various other fun QWidgets, all layed out dynamically using various QLayout objects... and what I'd like to do is occasionally make some of those widgets become invisible. That is, the invisible widgets would still take up their normal space in the window's layout, but they wouldn't be rendered: instead, the user would just see the window's background color in the widget's rectangle/area.
hide() and/or setVisible(false) won't do the trick because they cause the widget to be removed from the layout entirely, allowing other widgets to expand to take up the "newly available" space; an effect that I want to avoid.
I suppose I could make a subclass of every QWidget type that override paintEvent() (and mousePressEvent() and etc) to be a no-op (when appropriate), but I'd prefer a solution that doesn't require me to create three dozen different QWidget subclasses.
This problem was solved in Qt 5.2. The cute solution is:
QSizePolicy sp_retain = widget->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
widget->setSizePolicy(sp_retain);
http://doc.qt.io/qt-5/qsizepolicy.html#setRetainSizeWhenHidden
The only decent way I know of is to attach an event filter to the widget, and filter out repaint events. It will work no matter how complex the widget is - it can have child widgets.
Below is a complete stand-alone example. It comes with some caveats, though, and would need further development to make it complete. Only the paint event is overridden, thus you can still interact with the widget, you just won't see any effects.
Mouse clicks, mouse enter/leave events, focus events, etc. will still get to the widget. If the widget depends on certain things being done upon an a repaint, perhaps due to an update() triggered upon those events, there may be trouble.
At a minimum you'd need a case statement to block more events -- say mouse move and click events. Handling focus is a concern: you'd need to move focus over to the next widget in the chain should the widget be hidden while it's focused, and whenever it'd reacquire focus.
The mouse tracking poses some concerns too, you'd want to pretend that the widget lost mouse tracking if it was tracking before. Properly emulating this would require some research, I don't know off the top of my head what is the exact mouse tracking event protocol that Qt presents to the widgets.
//main.cpp
#include <QEvent>
#include <QPaintEvent>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
#include <QDialogButtonBox>
#include <QApplication>
class Hider : public QObject
{
Q_OBJECT
public:
Hider(QObject * parent = 0) : QObject(parent) {}
bool eventFilter(QObject *, QEvent * ev) {
return ev->type() == QEvent::Paint;
}
void hide(QWidget * w) {
w->installEventFilter(this);
w->update();
}
void unhide(QWidget * w) {
w->removeEventFilter(this);
w->update();
}
Q_SLOT void hideWidget()
{
QObject * s = sender();
if (s->isWidgetType()) { hide(qobject_cast<QWidget*>(s)); }
}
};
class Window : public QWidget
{
Q_OBJECT
Hider m_hider;
QDialogButtonBox m_buttons;
QWidget * m_widget;
Q_SLOT void on_hide_clicked() { m_hider.hide(m_widget); }
Q_SLOT void on_show_clicked() { m_hider.unhide(m_widget); }
public:
Window() {
QGridLayout * lt = new QGridLayout(this);
lt->addWidget(new QLabel("label1"), 0, 0);
lt->addWidget(m_widget = new QLabel("hiding label2"), 0, 1);
lt->addWidget(new QLabel("label3"), 0, 2);
lt->addWidget(&m_buttons, 1, 0, 1, 3);
QWidget * b;
b = m_buttons.addButton("&Hide", QDialogButtonBox::ActionRole);
b->setObjectName("hide");
b = m_buttons.addButton("&Show", QDialogButtonBox::ActionRole);
b->setObjectName("show");
b = m_buttons.addButton("Hide &Self", QDialogButtonBox::ActionRole);
connect(b, SIGNAL(clicked()), &m_hider, SLOT(hideWidget()));
QMetaObject::connectSlotsByName(this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"
You can use a QStackedWidget. Put your button on the first page, a blank QWidget on the second, and change the page index to make your button vanish while retaining its original space.
I've 3 solutions in my mind:
1) Subclass your QWidget and use a special/own setVisible() replacement method witch turns on/off the painting of the widget (if the widget should be invisible simply ignore the painting with an overridden paintEvent() method). This is a dirty solution, don't use it if you can do it other ways.
2) Use a QSpacerItem as a placeholder and set it's visibility to the opposite of the QWidget you want to hide but preserve it's position+size in the layout.
3) You can use a special container widget (inherit from QWidget) which gets/synchronizes it's size based on it's child/children widgets' size.
I had a similar problem and I ended up putting a spacer next to my control with a size of 0 in the dimension I cared about and an Expanding sizeType. Then I marked the control itself with an Expanding sizeType and set its stretch to 1. That way, when it's visible it takes priority over the spacer, but when it's invisible the spacer expands to fill the space normally occupied by the control.
May be QWidget::setWindowOpacity(0.0) is what you want? But this method doesn't work everywhere.
One option is to implement a new subclass of QWidgetItem that always returns false for QLayoutItem::isEmpty. I suspect that will work due to Qt's QLayout example subclass documentation:
We ignore QLayoutItem::isEmpty(); this means that the layout will treat hidden widgets as visible.
However, you may find that adding items to your layout is a little annoying that way. In particular, I'm not sure you can easily specify layouts in UI files if you were to do it that way.
Here's a PyQt version of the C++ Hider class from Kuba Ober's answer.
class Hider(QObject):
"""
Hides a widget by blocking its paint event. This is useful if a
widget is in a layout that you do not want to change when the
widget is hidden.
"""
def __init__(self, parent=None):
super(Hider, self).__init__(parent)
def eventFilter(self, obj, ev):
return ev.type() == QEvent.Paint
def hide(self, widget):
widget.installEventFilter(self)
widget.update()
def unhide(self, widget):
widget.removeEventFilter(self)
widget.update()
def hideWidget(self, sender):
if sender.isWidgetType():
self.hide(sender)
I believe you could use a QFrame as a wrapper. Although there might be a better idea.
Try void QWidget::erase (). It works on Qt 3.

Resources