Keeping the aspect ratio of a sub-classed QWidget during resize
Asked Answered
Z

2

15

I'm creating a new widget, by subclassing the QWidget class. I'd like to be able to set a ratio (for its height and its width) for this widget, which will always be maintained.

For this, I've always searched, using the Qt5 documentation, Google, and Stackoverflow. Obviously, I've found answers: in particular, this one. But, unfortunately, not even one is fully effective:

  • Setting the sizeIncrement does totally nothing, even if the widget is a window
  • I tried to overload resizeEvent, but I really don't know how to do this...
  • If I follow this answer, two things:

    1. If the widget is a top-level window, the ratio isn't maintained at all, I can resize it as I want.
    2. If I place this widget in a layout, if I just increase both width and height of the window, the ratio is maintained. But as soon as I increase the width or the height to much, the widget is flattened. Instead, I would like that the layout automatically adjust its size to keep the widget's ratio.

So, how could I manage to keep the aspect ratio of a subclassed QWidget?

Zito answered 2/5, 2015 at 17:49 Comment(3)
Here is another post I worked on that works well, at least for QLabels. #8212482Manella
Have you tried reimplementing QWidget::heightForWidth()? That allows you to specify an aspect ratio. If that's not enough, create a parent "dummy" QWidget to hold your widget in a 3x3 QGridLayout, with your widget in the middle and QSpacerItem's around the edges (see QGridLayout::addItem()). The QSpacerItem's can provide the extra spacing (or be sized down to 0) as needed when resizing. You'd probably need to use QGridLayout::setColumnStretch() and QGridLayout::setRowStretch().Talamantes
Thanks both of you, your solutions work, but the same issue remains: if I try to increase solely the width of the widget, then the aspect ratio isn't maintained anymore.Zito
T
17

Create a parent widget (e.g., AspectRatioWidget) in which to place your widget. For the parent widget, subclass QWidget and give it a QBoxLayout. Put your widget into the center, and QSpacerItems on either side. Then in the parent widget's QWidget::resizeEvent adjust the direction and stretches as needed. I've provided an example below. To use, just create an instance of AspectRatioWidget and pass the constructor a pointer to your widget and the desired aspect ratio.

// header
class AspectRatioWidget : public QWidget
{
public:
    AspectRatioWidget(QWidget *widget, float width, float height, QWidget *parent = 0);
    void resizeEvent(QResizeEvent *event);

private:
    QBoxLayout *layout;
    float arWidth; // aspect ratio width
    float arHeight; // aspect ratio height
};

// cpp
AspectRatioWidget::AspectRatioWidget(QWidget *widget, float width, float height, QWidget *parent) :
    QWidget(parent), arWidth(width), arHeight(height)
{
    layout = new QBoxLayout(QBoxLayout::LeftToRight, this);

    // add spacer, then your widget, then spacer
    layout->addItem(new QSpacerItem(0, 0));
    layout->addWidget(widget);
    layout->addItem(new QSpacerItem(0, 0));
}

void AspectRatioWidget::resizeEvent(QResizeEvent *event)
{
    float thisAspectRatio = (float)event->size().width() / event->size().height();
    int widgetStretch, outerStretch;

    if (thisAspectRatio > (arWidth/arHeight)) // too wide
    {
        layout->setDirection(QBoxLayout::LeftToRight);
        widgetStretch = height() * (arWidth/arHeight); // i.e., my width
        outerStretch = (width() - widgetStretch) / 2 + 0.5;
    }
    else // too tall
    {
        layout->setDirection(QBoxLayout::TopToBottom);
        widgetStretch = width() * (arHeight/arWidth); // i.e., my height
        outerStretch = (height() - widgetStretch) / 2 + 0.5;
    }

    layout->setStretch(0, outerStretch);
    layout->setStretch(1, widgetStretch);
    layout->setStretch(2, outerStretch);
}
Talamantes answered 2/5, 2015 at 23:0 Comment(2)
Thanks! This solution is the only one that worked for me, subclassing QOpenGLWidget. I added layout->setContentsMargins(0, 0, 0, 0); in the constructor to remove the margins.Fictile
@Anothoy Random question, but is there a way of using two of these AspectRatioWidgets in a QHBoxLayout with stretches on either side and still have them fill the space? When I use addStretch on either side of the aspect ratio widgets, it forces them to their minimum size and they doesnt enlarge/shrink anymore.Officialism
S
9

I have rewritten Anthony's code in Python/PySide2:

from PySide2.QtWidgets import QBoxLayout, QSpacerItem, QWidget


class AspectRatioWidget(QWidget):
    def __init__(self, widget, parent):
        super().__init__(parent)
        self.aspect_ratio = widget.size().width() / widget.size().height()
        self.setLayout(QBoxLayout(QBoxLayout.LeftToRight, self))
        #  add spacer, then widget, then spacer
        self.layout().addItem(QSpacerItem(0, 0))
        self.layout().addWidget(widget)
        self.layout().addItem(QSpacerItem(0, 0))

    def resizeEvent(self, e):
        w = e.size().width()
        h = e.size().height()

        if w / h > self.aspect_ratio:  # too wide
            self.layout().setDirection(QBoxLayout.LeftToRight)
            widget_stretch = h * self.aspect_ratio
            outer_stretch = (w - widget_stretch) / 2 + 0.5
        else:  # too tall
            self.layout().setDirection(QBoxLayout.TopToBottom)
            widget_stretch = w / self.aspect_ratio
            outer_stretch = (h - widget_stretch) / 2 + 0.5

        self.layout().setStretch(0, outer_stretch)
        self.layout().setStretch(1, widget_stretch)
        self.layout().setStretch(2, outer_stretch)
Starry answered 16/4, 2018 at 7:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.