Move layout to another layout in Qt5 - c++

I have a custom container widget in Qt5 which has a QFrame without a layout. I use setLayout on QFrame which works fine but sometimes I want to transfer an existing layout which contains widgets and other layouts to that QFrame as layout. Is that even possible in Qt5 to transfer layouts already added with addLayout?

No problem. Just make the layout the layout of the target widget with setLayout(). The widgets contained will be reparented automatically.
void MainWindow::moveLayout()
{
// Move the layout from ui->g0 to ui->g1 and vice versa
// every time you call moveLayout
static int toggle = 0;
QLayout* l;
QWidget* w;
if (toggle == 0)
{
l = ui->g0->layout();
w = ui->g1;
}
else
{
l = ui->g1->layout();
w = ui->g0;
}
if (w->layout())
{
// Hack to clean target widget
QWidget z;
z.setLayout(w->layout());
}
w->setLayout(l);
toggle = 1 - toggle;
}

Related

QWidget Overflow

I have a main QGridLayout on the dialog I'm working on. It's split into content-containers (QWidgets with their own QVBoxLayout) of varying sizes, each assigned a particular number of grid-columns within the grid layout (depending on their needs).
Each content-container holds some buttons. Some of these buttons should span across 2 content-containers. The way I'm trying to achieve this is by overflowing these 2-container buttons outside their layout, while keeping the size of the content-containers the same (what it was allocated according to the number of columns within the grid). I have no idea how to go about this, because either the size of the containers changes, or the QWidget doesn't overflow.
Is there a way to set some sort of overflow property on the button, or do I have to just place the buttons on the grid? I'm trying to avoid doing this because I think it could be messy once new requirements arise and I'll have to recalculate their positioning.
Here is an image of what I'm trying to do:
Here is the relevant code:
ModeDialog::ModeDialog(MainWindow *pMainWindow)
: XDialog(pMainWindow, tr("Operating Mode"), true)
{
XDialog::AddButton(tr("Exit"), QDialogButtonBox::AcceptRole);
ConstructStylingFromTemplates();
CSettings* Settings = pMainWindow->GetSettings();
QString SlotString1, SlotString2;
QGridLayout* mp_MainLayout = new QGridLayout();
mp_MainLayout->setContentsMargins(10, 30, 10, 20);
// Construct Channel Group Layouts with Channel Containers
for (int i = 0; i < 3; i++)
{
switch(i)
{
case 0: { SlotString1 = "A"; SlotString2 = "B"; break; }
case 1: { SlotString1 = "C"; SlotString2 = "D"; break; }
case 2: { SlotString1 = "E"; SlotString2 = "F"; break; }
}
QHBoxLayout* ChannelGroupLayout = new QHBoxLayout();
if (CSettings_RR::IsE1T1Channel(Settings, i*2))
{
AddChannelToChannelGroup(ChannelGroupLayout, SlotString1);
AddChannelToChannelGroup(ChannelGroupLayout, SlotString2);
}
else if(CSettings_RR::IsPtpChannel(Settings, i*2))
{
AddChannelToChannelGroup(ChannelGroupLayout, SlotString1);
}
else if(CSettings_RR::IsOtaChannel(Settings, i*2))
{
AddChannelToChannelGroup(ChannelGroupLayout, SlotString1);
}
else
{
continue;
}
mp_MainLayout->addLayout(ChannelGroupLayout, 0, i*2, 1, 2);
}
SetContentLayout(mp_MainLayout);}
void ModeDialog::AddChannelToChannelGroup(QHBoxLayout* ChannelGroupLayout, QString SlotString)
{
QVBoxLayout* ChannelLayout = new QVBoxLayout();
// Add Label to Channel Layout
XLabel* ChannelLabel = new XLabel("Channel " + SlotString, m_textSize, true, Qt::AlignCenter | Qt::AlignVCenter, this);
ChannelLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
ChannelLayout->addWidget(ChannelLabel);
// Add Container to Channel Layout
QWidget* ChannelContainer = new QWidget();
ChannelContainer->setStyleSheet(m_styleSheetChannelContainer);
ChannelLayout->addWidget(ChannelContainer);
//WIP - add buttons to container
QVBoxLayout* ChannelContainerLayout = new QVBoxLayout();
ChannelContainer->setLayout(ChannelContainerLayout);
XPushButton* ModeButton = new XPushButton(tr("CLOCK"), 160, 40, this, true);
ChannelContainerLayout->addWidget(ModeButton);
//WIPEND
// Add Channel Layout to Channel Group Layout
ChannelGroupLayout->addLayout(ChannelLayout);
}
It is not possible to overflow using QWidgets.
But it is possible with QML.
By default, items will overflow and you have to manage their positions and sizes (and z-index to tell who overlaps who) : http://doc.qt.io/qt-5/qtquick-positioning-topic.html
You have to use QML layouts or set the item's parent property clip to true to avoid the overflow.
There is no way to overflow widgets in Qt, not even if you don't use a layout. A widget will always be limited to its parent's bounds.

Widgets with FixedSize change their size at will

I have a QGridLayout inside a center widget, which contains a supposedly fixed-sized widget (referred to as inner widget) that also has a QGridLayout filled with buttons. Inner widget's size is determined by how many buttons are there in the grid, and is supposed to be an exact fit (no spacing between the buttons, FixedSize policy applied in buttons' constructor), and all buttons have their sizes and policies set in the constructor. Now, if I don't put inner widget into a layout of any kind, it works just fine, I get nice square buttons. But if I put inner widget into a grid layout, all buttons suddenly change their sizes, and widget also doesn't seem like keeping its size. Why?
Edit: MyButtonTable:
MyButtonTable::MyButtonTable(QWidget *parent) : QWidget(parent), array()
{
size_x = 2;
size_y = 2;
QGridLayout* layout = new QGridLayout();
for(size_t x = 0; x < size_x; x++) {
this->array.push_back(std::vector<MyRightClickButton*>());
}
for(size_t x = 0; x < size_x; x++) {
for(size_t y = 0; y < size_y; y++) {
this->array[x].push_back(new button_t());
QObject::connect(array[x][y], SIGNAL(rightClicked()), this, SLOT(internalRightClick()));
QObject::connect(array[x][y], SIGNAL(clicked()), this, SLOT(internalClick()));
layout->addWidget(array[x][y], x, y);
}
}
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
this->setLayout(layout);
this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
this->setMinimumSize(QSize(0,0));
this->resize(QSize(10*size_y,10*size_x));
}
MyRightClickButton(QWidget *parent = 0):QPushButton(parent) {
marked = false;
this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
this->setMinimumSize(QSize(0,0));
this->resize(QSize(10,10));
}
A layout manager is used to arrange child widgets in them. The arrangement designates each layout manager. Layout managers adjust the size of their child widgets based on the resize policies. Even if you are setting a fixed size for a child widget, using resize() or setGeometry(), they will be resized by the layout manager, if you did not set the resize policy of the child widget.
For example
widget->setFixedSize (100, 100);
widget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
This is how it should be done.

How to put QGraphicsEffect on QScrollBar inside QScrollArea?

I try to set QGraphicsDropShadowEffect on a QScrollBar. This code works:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setColor(Qt::red);
dse->setOffset(0);
ui->verticalScrollBar->setGraphicsEffect(dse); // verticalScrollBar is `QScrollBar`.
However the following does not:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setColor(Qt::red);
dse->setOffset(0);
ui->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
ui->scrollArea->verticalScrollBar()->setGraphicsEffect(dse);
In the second example code I try to set effect on a slider inside QScrollArea but it does not apply to it. However, it can be applied to the whole scrollArea by ui->scrollArea->setGraphicsEffect(dse). What am I doing wrong?
The problem I had was caused by parent widget of QScrollBar. So, QScrollArea has item area and scroll bar area. Scroll bar area contains QWidgets and QScrollBars actually placed on these QWidgets. So, to make this work I actually had to set effect for the parent widget:
for(auto *child : ui->scrollArea->findChildren<QScrollBar*>()) {
if (child->orientation() == Qt::Vertical) {
auto * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setColor(Qt::red);
dse->setXOffset(-3);
dse->setYOffset(0);
child->parentWidget()->setGraphicsEffect(dse);
qDebug() << child->metaObject()->className(); // QScrollBar
qDebug() << child->parentWidget()->metaObject()->className(); // QWidget
}
}

Hiding a vertical layout programmatically?

I wanted to know if its possible to hide a vertical layout. I currently have a a horizontal layout with two vertical layouts.I wanted to hide one of the vertical layouts(with all its content) on button click. Any suggestions on how I could do that.
As #jmk said, you need to use a QWidget. I'll just add that it's very easy to turn an existing horizontal or vertical layout into a widget from Qt Designer by right-clicking on it and selecting Morph Into->QWidget:
The layout is entirely preserved, but now you can show/hide the layout box because it's an ordinary widget with that layout.
Instead of inserting vertical layouts directly into your top-level horizontal layout, use container widgets to easily control visibility:
// Create your left and right widgets
QWidget* leftWidget = new QWidget();
QVBoxLayout* leftLayout = new QVBoxLayout(leftWidget);
QWidget* rightWidget = new QWidget();
QVBoxLayout* rightLayout = new QVBoxLayout(rightWidget);
// Populate your vertical layouts here ...
QHBoxLayout* horizontalLayout = new QHBoxLayout(parentWidget);
horizontalLayout->addWidget(leftWidget);
horizontalLayout->addWidget(rightWidget);
Then, you can simply hide or show leftWidget or rightWidget to effectively control the visibility of everything in the vertical layouts that you have, without having to hide/show each individual widget.
My suggestion:
// l is the layout pointer
for (int i = 0; i != l->count(); ++i) {
QWidget* w = qobject_cast<QWidget*>(l->itemAt(i));
if (w != 0) {
w->setVisible(false); // hides the widget
}
else {
// do some recursive things with the layout
}
}
(Hope it works ;))
The widget is basically invisible.

Qt: How to force a hidden widget to calculate its layout?

What I am trying to do is render a qwidget onto a different window (manually using a QPainter)
I have a QWidget (w) with a layout and a bunch of child controls. w is hidden. Until w is shown, there is no layout calculations happening, which is expected.
When I call w->render(painter, w->mapToGlobal(QPoint(0,0)), I get a bunch of controls all overlapping each other.
w->layout()->activate();w->layout()->update() doesn't seem to do anything.
Is there a way to force the layout to happen without showing w?
Forcing a layout calculation on a widget without showing it on the screen:
widget->setAttribute(Qt::WA_DontShowOnScreen);
widget->show();
The show() call will force the layout calculation, and Qt::WA_DontShowOnScreen ensures that the widget is not explicitly shown.
The layout calculation of a widget can be forced by calling invalidate() followed by activate() on its layout, even if the widget is hidden. This also causes the widget's size() and sizeHint() functions to return correct and updated values, even if show() has not yet been called on that widget.
It is however necessary to care about all child widgets and layouts recursively, as a layout recalculation request doesn't automatically propagate to the childs.
The following code shows how to do this.
/**
* Forces the given widget to update, even if it's hidden.
*/
void forceUpdate(QWidget *widget) {
// Update all child widgets.
for (int i = 0; i < widget->children().size(); i++) {
QObject *child = widget->children()[i];
if (child->isWidgetType()) {
forceUpdate((QWidget *)child);
}
}
// Invalidate the layout of the widget.
if (widget->layout()) {
invalidateLayout(widget->layout());
}
}
/**
* Helper function for forceUpdate(). Not self-sufficient!
*/
void invalidateLayout(QLayout *layout) {
// Recompute the given layout and all its child layouts.
for (int i = 0; i < layout->count(); i++) {
QLayoutItem *item = layout->itemAt(i);
if (item->layout()) {
invalidateLayout(item->layout());
} else {
item->invalidate();
}
}
layout->invalidate();
layout->activate();
}
Try with the QWidget::sizeHint() method, which is supposed to return the size of the widget once laid out.
I had some succes in a similar problem by first calling w->layout()->update() before w->layout()->activate(). That seems to force the activate() to actually do something rather than think it is fine because the window isn't being shown anyway.
When going through QWidget::grab() I noticed this part:
if (r.width() < 0 || r.height() < 0) {
// For grabbing widgets that haven't been shown yet,
// we trigger the layouting mechanism to determine the widget's size.
r = d->prepareToRender(QRegion(), renderFlags).boundingRect();
r.setTopLeft(rectangle.topLeft());
}
QWidget::grab() has been introduced in Qt 5.0, and this test function with a QDialog containing a layout seems to work on Qt 5.5.1
int layoutTest_2(QApplication& a)
{
CLayoutTestDlg dlg; // initial dlg size can also be set in the constructor
// https://stackoverflow.com/questions/21635427
QPixmap pixmap = dlg.grab(); // must be called with default/negative-size QRect
bool savedOK = pixmap.save("E:/temp/dlg_img.png");
// saving is not necessary, but by now the layout should be done
dlg.show();
return a.exec();
}
This worked for me when using the sizeHint() plus translating the painter, however I do this inside the paint() method.
void ParentWidget::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->save();
painter->translate(option.rect.topLeft());
w->render(painter);
painter->restore();
}
In this case, option.rect.topLeft() gives me the correct placement. You should try a more sensible coordinate instead of w->mapToGlobal(QPoint(0,0).