[Solved]Trying to fix aspect ratio of a widget. Big problems with layouts.



  • I cannot believe how much I am struggling with layouts and re-sizing in Qt. I have read so much documentation on layouts, sizeHint and sizePolicy. It all seems logical and comprehensible, but nothing seems to work...

    I am trying to fix the aspect ratio of a widget to 640:480. the widget is contained in an extension of QFrame, which has its layout set to QVBoxLayout.

    Within the widget constructor I have:
    @ sizePolicy().setWidthForHeight(true);
    @

    I define 2 member functions:

    @QSize TerrainView::sizeHint() {
    return QSize(640, 480);
    }

    int TerrainView::heightForWidth(int w) {
    return w*(480.0/640.0);
    }
    @

    I have also tried setting sizePolicy to all of:

    @QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)
    QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)
    QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)
    QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)
    @

    None of which change the behaviour in the slightest. The widget continue to resize to fill all the available space in the containing QFrame.

    Now the widget itself, does not have a layout (I don't think it is necessary as it does not have any child widgets to draw). The widget is simply painted during paintEvent. As I understand it the layout within which the widget is contained should take responsibility for sizing it according to its sizePolicy, and sizeHint.

    Please can somebody with some experience tell me what I am missing.



  • Hi, there are three errors exist in your code, so the size hint and policy you set can not be accepted by QLayout.

    @
    Within the widget constructor I have:
    sizePolicy().setWidthForHeight(true);
    @

    Here, you operate on one temporary variable. so it does nothing.

    @
    QSize TerrainView::sizeHint() {
    return QSize(640, 480);
    }

    int TerrainView::heightForWidth(int w) {
      return w*(480.0/640.0);
    }
    

    @

    Both of them should be const member function.



  • Thank you very much Robot Herder ! Missing 'const' off the declarations of sizeHint and heightForWidth was causing me a lot of confusion. The functions were failing to override the appropriate virtual functions and wer completely ignored. This means all the experimenting I did to try and make the functions work was in vain. You have saved me a lot of time.

    With respect to sizePolicy returning a temporary copy, I now have the following in the QWidgets's constructor:

    @
    //Setup resize behaviour
    QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Preferred);
    sp.setHeightForWidth(true);
    setSizePolicy(sp);
    @

    My heightForWidth function is now called during re-sizing. Setting the size policy to Fixed results in the widget retaining a size of 640*480 within the containing frame. It floats in the centre of the frame. This did not happen before adding the const declarations. However I cannot find any setting that causes the aspect-ratio to remain fixed as I had hoped. I thought that the width would expand to fill the frame and that the height would be calculated from the width. The widget s within a QFrame which does have a layout assigned. Any more ideas ?



  • Solved !

    Although much has been written on using heightForWidth to fix the aspect-ratio of a widget, nobody seems to have been successful. I believe this is because the function is used by QWidget in a way that is inconsistent with fixing aspect-ratio. Afterall it is designed for trading height against width in menus and word-wrapping text fields.

    I tried another technique, setting the viewport of the widget to centre and size it's contents and it was actually very simple to make a fixed aspect-ratio image area. For anyone who needs to do this, the important functions are shown below:

    @
    double ForwardView::imageAspectRatio() {
    return ((double) currentImage.width()) / ((double) currentImage.height());
    }

    QRect ForwardView::centeredViewport(int width, int height) {
    double aspectRatio = imageAspectRatio();
    int heightFromWidth = (int) (width/aspectRatio);
    int widthFromHeight = (int) (height*aspectRatio);

    if (heightFromWidth<=height) {
    return QRect(0,(height-heightFromWidth)/2, width, heightFromWidth);
    } else {
    return QRect((width-widthFromHeight)/2.0, 0, widthFromHeight, height);
    }
    }

    void ForwardView::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setViewport(centeredViewport(width(), height()));
    painter.drawImage(QRect(QPoint(0,0), size()), img);
    }

    @

    The widget will contain the largest image possible without changing its aspect-ratio (no stretching distortion). The image is centered in the unused vertical or horizontal space.

    I found an example on the internet of a custom layout which achieves the same effect, but this seems a very lengthy way of achieving the same. Also the layout only accepts a single widget. This seems to be an inappropriate and odd use of a layout as they normally manage multiple child widgets.



  • Thanks for posting the solution.
    Don't forget to change the title of your thread - add a [Solved] at its beginning as this will help others.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.