QWidget resizing problems - c++

I'm fighting for hours with following problem.
Widget A is container with QHBoxLayout for QLabel objects. I want to put such widget in other widget, with constant height equal to A's height, and minimum width equal to A's width (but width can be changed).
In A constructor I have following code:
QHBoxLayout* mLayout = new QHBoxLayout();
for (auto e : wordsList)
mLayout->addWidget(e);
mLayout->setSpacing(0);
mLayout->setMargin(0);
mLayout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(mLayout);
And there is the body of B:
class B : public QWidget
{
Q_OBJECT
public:
explicit B(A *a0, QWidget *parent = 0)
: QWidget(parent),
a(a0)
{
a->setParent(this);
setSizePolicy(QSizePolicy());
setFixedSize(sizeHint());
}
QSize minimumSize() const
{
return a->size();
}
QSize sizeHint() const
{
return minimumSize();
}
private:
A *a;
};
In main() I call show() on B's instance. In effect, I get big, fully resizeable window, bigger than A. A is frozen, doesn't resize with B.
I want B to start with the same size as A and to be resizeable only horizontally (but with minimum width equal to A).
How can I achieve this?
And why setSizePolicy() and setFixedSize(), minimumSize() and even sizeHint() don't work?
Can this be because I use B as a main window?
If I use layout in B, this probably should be another QHBoxLayout. But when he has too much space, he fits his component, so I would need to use QSpacerItem and resize him always, when B is resizing. But I don't know order, in which widgets are receiving QResizeEvent: if B gets it before my spacer, I will do nothing. I probably could use eventFilter, but still, this is too complicated (behaviour I need is pretty simple!).

I think there is no need to inherit B you can just add a layout in B and add A to the layout:
QWidget * widgetB = new WhateverWidgetYouWant();
QHBoxLayout* layoutForB = new QHBoxLayout();
widgetB->setLayout(mLayout);
layoutForB->addWidget(pointerToA);
What you need is to change the size policy of A to Expanding or Preffered or experiment with some other policies. You can test this in the designer and when you are satisfied with the results you can get the right properties and set them in your code.

Related

Is it possible to constrain a Qt parent QWidget to always be the same size as its child QWidget?

I've written a simple "proxy widget" class in Qt; the idea is that this widget will hold a single child QWidget and represent that widget in the QWidget hierarchy. (FWIW, the motivation for doing this is to make it easy to move the child widget around the hierarchy without having to directly disturb the state of various other container-QWidgets to do so).
This seems to work fairly well; the only problem I've run into is that I want my ProxyWidget to always be laid-out the same way as its child-QWidget would be (if the child had been added to the widget hierarchy directly); but instead I find that the ProxyWidget is often sized larger than its child would be, leading to wasted space in the GUI.
Therefore, is there some way I can craft my ProxyWidget class so that Qt's layout managers to move/size it exactly the same as if its child widget was added directly?
As a minimal test/example, you can compile the following code and run it with or without the "proxy" command line argument -- my goal is that the visual results would be the same either way, and in particular that you would never see any red pixels in the window (since red pixels indicate areas where the ProxyWidget has been sized larger than the blue child widget it contains)
#include <QApplication>
#include <QStackedLayout>
#include <QWidget>
class ProxyWidget : public QWidget
{
public:
ProxyWidget(QWidget * childWidget)
: _childWidget(childWidget)
, _layout(new QStackedLayout(this))
{
_layout->addWidget(childWidget);
setSizePolicy(childWidget->sizePolicy());
}
virtual QSize sizeHint() const {return _childWidget->sizeHint();}
virtual QSize minimumSizeHint() const {return _childWidget->minimumSizeHint();}
private:
QWidget * _childWidget;
QStackedLayout * _layout;
};
static void SetWidgetBackgroundColor(QWidget * w, const QColor bc)
{
QPalette p = w->palette();
p.setColor(QPalette::Window, bc);
w->setAutoFillBackground(true);
w->setPalette(p);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QWidget * win = new QWidget;
win->setWindowTitle("Proxy Widget test");
QWidget * proxyMe = new QWidget;
proxyMe->setFixedSize(100, 50);
SetWidgetBackgroundColor(proxyMe, Qt::blue);
QBoxLayout * winLayout = new QBoxLayout(QBoxLayout::TopToBottom, win);
if ((argc >= 2)&&(strcmp(argv[1], "proxy") == 0))
{
ProxyWidget * proxyWidget = new ProxyWidget(proxyMe);
SetWidgetBackgroundColor(proxyWidget, Qt::red);
winLayout->addWidget(proxyWidget);
}
else winLayout->addWidget(proxyMe);
win->show();
return app.exec();
}
I guess minimumSize and maximumSize of ProxyWidget is different from its child widget and setting them fix things in your particular example :
ProxyWidget(QWidget * childWidget)
: _childWidget(childWidget)
, _layout(new QStackedLayout(this))
{
_layout->addWidget(childWidget);
setSizePolicy(childWidget->sizePolicy());
this->setMinimumSize(childWidget->minimumSize());
this->setMaximumSize(childWidget->maximumSize());
}
However i am not sure it's the best solution but it might gives you a hint to a better one.

Qt: organizing my layout and QSizePolicy

The main window of my Qt/C++ program looks like this:
As you can see on the picture, the window consists essentially of:
a menu on the left
two "canvases"
What I want is straightforward to describe: I want that under resizing of the window, both canvases take as much space as possible, but still remain squares (width = height). I've been trying to achieve that unsuccessfully.
Let me quickly describe the objects:
The window is a class Window that I created deriving QWidget. It has a QGridLayout for a layout.
The window's layout has three widgets: the left menu LeftMenu *menu, and the canvases Canvas *leftCanvas, *rightCanvas. Both LeftMenu and Canvas are custom classes deriving QWidget.
(NB: the left menu actually consists of 3 different widgets (submenus), and the window also has a status bar and a top menu, but I don't think it matters for my question.)
I have been "playing" (not having fun the least bit) with QSizePolicy's etc to try to get the Canvases' sizes to behave like I want (be as large as possible inside the window, but keep height/width ratio = 1), unsuccessfully. Let me describe my latest attempt in case that is useful for you (if you already know a solution to my problem, you don't have to keep reading):
I overrode the methods heightForWidth(), sizeHint() and minimumSizeHint() for Canvas like so:
class Canvas : public QWidget
{
Q_OBJECT
friend class Window;
public:
explicit Canvas(Window* window);
...
private:
void resizeEvent(QResizeEvent *resizeEvent) override;
int heightForWidth(int width) const override {return width;}
QSize sizeHint() const override
{
int size = std::min(width(), height());
return QSize(size, size);
}
QSize minimumSizeHint() const override {return QSize(200,200);}
...
};
And the constructor of my class Window looks like (a bit simplified):
Window::Window(ActionHandler *handler)
{
leftMenu = new LeftMenu(this);
leftMenu->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
leftCanvas = new Canvas(this);
rightCanvas = new Canvas(this);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Maximum);
policy.setHeightForWidth(true);
leftCanvas->setSizePolicy(policy);
rightCanvas->setSizePolicy(policy);
layout = new QGridLayout;
layout->setColumnMinimumWidth(0, menuWidth());
layout->addWidget(leftMenu, 0, 0);
layout->addWidget(leftCanvas, 0, 1);
layout->addWidget(rightCanvas, 0, 2);
setLayout(layout);
}
My idea was that as long as the width of the canvases is the limiting factor, the sizePolicy of the canvases should be (QSizePolicy::Expanding, QSizePolicy::Maximum). And as soon as the height of the canvases becomes the limiting factor, I would change the sizePolicy of the canvases (probably in Canvas::resizeEvent()) to the opposite: (QSizePolicy::Maximum, QSizePolicy::Expanding). Does that sound too complicated?
Anyway, it already fails, and I don't understand why. If I shrink the window horizontally it gives me this:
So, the height of the canvases does not shrink. I do not understand this behavior. In the Qt documentation (http://doc.qt.io/qt-4.8/qsizepolicy.html#Policy-enum), I read:
QSizePolicy::Maximum The sizeHint() is a maximum. The widget can be shrunk any amount without detriment if other widgets need the space (e.g. a separator line). It cannot be larger than the size provided by sizeHint().
The behavior of my canvases here seems to contradict this: their height is larger than the height provided by their sizeHint(). (I did make sure by std::couting "live": the canvas sizeHint, its height, its sizePolicy and its hasHeightForWidth parameters).

How do I get the size of an area of a QVBoxLayout?

This is the structure I have. I want to find the total size of label_2, or the QVBoxLayout, as it's displayed. When I use verticalLayout_2->width(), I always get 100 and verticalLayout_2->height() always returns 30. It's set to expanding, so I thought it would fill the area, which is 385x379, according to Qt Creator.
Doing label_2-width() and label_2-height() also results in 100x30, regardless of the window size and the area I thought it would expand to.
There is nothing much visually going on after your widget's constructor has been run. The setupUi call happens in the constructor. The real work happens once the event loop gets going.
Your real problem is that you should not be checking the size at an arbitrary point in time. You should be checking it each time it changes. To do this, you need your own layout. All it takes is to derive from an existing layout, and reimplement setGeometry. This method is called each time the parent widget or parent layout resizes the given layout. That is the only correct approach, and it doesn't require any hacks to accomplish.
For example, the following class could be used to signal when there's a new geometry:
class SigBoxLayout : public QBoxLayout {
Q_OBJECT
protected:
void setGeometry(const QRect & r) Q_DECL_OVERRIDE {
if (r != geometry()) emit hasNewGeometry(r);
QBoxLayout::setGeometry(r);
}
public:
SigBoxLayout(QBoxLayout::Direction dir, QWidget * parent = 0) :
QBoxLayout(dir, parent) {}
Q_SIGNAL void hasNewGeometry(const QRect & r);
};

Derrived Widget not centered on parent when shown as dialog

I have a class MyListWidget derrived from QWidget. I passed parent and flags to the base class QWidget constructor (tried both Qt::Dialog and Qt::Popup in tests) but the custom widget is shown in the center of the screen instead centered to its parent.
MyListWidget* myListWidget = new MyListWidget(this, Qt::Dialog);
This is the constructor:
MyListWidget::MyListWidget(QWidget* parent, Qt::WindowFlags flags)
: QWidget(parent, flags),
ui(std::auto_ptr<Ui::MyListWidget>(new Ui::MyListWidget))
{
ui->setupUi(this);
}
If I put this widget into a separate dialog, anything works as expected. But why?
Wrapping works:
QDialog* popup = new QDialog(this, Qt::Popup);
QVBoxLayout* hLayout = new QVBoxLayout(popup);
// ... doing list creation like above
hLayout->addWidget(mmyListWidget);
popup->setLayout(hLayout);
const int width = mapListWidget->width();
const int height = mapListWidget->height();
popup->resize(width, height);
Any ideas what could happend here?
QWidget is not shown on center by default, so you need to center it manually (you can do that in the constructor):
MyListWidget::MyListWidget(QWidget* parent, Qt::WindowFlags flags)
: QWidget(parent, flags),
ui(std::auto_ptr<Ui::MyListWidget>(new Ui::MyListWidget))
{
ui->setupUi(this);
move(
parent->window()->frameGeometry().topLeft() +
parent->window()->rect().center() - rect().center()
);
}
P.S. Beware of std::auto_ptr, you probably want to use std::unique_ptr these days.
I'm not quite sure what you're trying to achieve but I have the feeling you should derive MyListWidget from QDialog.
Regards,
Ben

How do I place the QScrollBar on the content of the QScrollArea?

I want to make a scrollbar that fades in and out depending on usage. I subclassed QScrollBar and got the look that I want. The problem is that the scrollbar is placed next to the content. How do I instead make it go on top of the content?
I created a new QScrollbar which I connected to the original via signals and then used widget->setParent and then widget->setGeometry() to paint it on top
I quicker solution is to reparent the QScrollBars that the QScrollArea creates and add it to a new QLayout to position it how you want.
QScrollArea *scrollArea = new QScrollArea();
QScrollBar *scrollBar = scrollArea->horizontalScrollBar();
scrollBar->setParent(scrollArea);
scrollBar->setFixedHeight(20);//required for later
QVBoxLayout *scrollAreaLayout = new QVBoxLayout(scrollArea);
scrollAreaLayout->setContentsMargins(0, 0, 0, 10);//use whatever margins you want
scrollAreaLayout->addStretch(1);
scrollAreaLayout->addWidget(scrollBar);
This gets the basic functionality working, however the QScrollArea still adds space where the scrollbar would have been. To remove this, subclass QProxyStyle and override pixelMetric().
#include <QProxyStyle>
class StyleFixes : public QProxyStyle
{
public:
int pixelMetric(PixelMetric metric, const QStyleOption *option = Q_NULLPTR, const QWidget *widget = Q_NULLPTR) const override
{
if(metric == PM_ScrollBarExtent)
{
return 0;
}
return QProxyStyle::pixelMetric(metric, option, widget);
}
};
Then just apply it in main.cpp
QApplication::setStyle(new StyleFixes);
This will remove the arrows on the scrollbar however so you'll need to style it yourself.