9

I'm creating a screen where users can add certain tiles to use in an editor, but when adding a tile the window does not correctly resize to fit the content. Except that when I drag the window or resize it even just a little then it snaps to the correct size immediately.

enter image description here
And when just dragging the window it snaps to the correct size.

enter image description here

I tried using resize(sizeHint()); which gave me an incorrect size and the following error, but the snapping to correct size still happens when resizing/dragging.

QWindowsWindow::setGeometry: Unable to set geometry 299x329+991+536 on QWidgetWindow/'TileSetterWindow'. Resulting geometry:  299x399+991+536 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 259x329, maximum size: 16777215x16777215).

I also tried using updateGeometry() and update(), but it didn't seem to do much if anything.

When setting the window to fixedSize it will immediately resize, but then the user cannot resize the window anymore. What am I doing wrong here and where do I start to solve it?

Edit Minimal verifiable example and the .ui file. selected_layout is of type Flowlayout The flowlayout_placeholder_1 is only there because I can't place a flowlayout directly into the designer.

Edit2 Here is a minimal Visual Studio example. I use Visual Studio for Qt development. I tried creating a project in Qt Creator, but I didn't get that to work.

Edit3 Added a little video (80 KB).

Edit4 Here is the updated Visual Studio example. It has the new changes proposed by jpo38. It fixes the issue of the bad resizing. Though now trying to downsize the windows causes issues. They don't correctly fill up vertical space anymore if you try to reduce the horizontal space even though there is room for more rows.

Eejin
  • 770
  • 1
  • 8
  • 29
  • 2
    provide a [mcve] – eyllanesc Feb 16 '18 at 17:34
  • share the .ui file, not the .h file generated by the .ui since it is easier to manage. – eyllanesc Feb 16 '18 at 18:28
  • Did you try to use `adjustSize()`? – Evgeny Feb 16 '18 at 20:16
  • @Evgeny I did and I get "QWindowsWindow::setGeometry: Unable to set geometry 299x329+671+417 on QWidgetWindow/'TileSetterWindow'. Resulting geometry: 299x399+671+417 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 259x329, maximum size: 16777215x16777215)." It doesn't seem to do much except making the initial window size less width than it is set in the designer. – Eejin Feb 18 '18 at 14:47
  • Please give also .pro file so then the example will be complete. – karol Feb 23 '18 at 22:17
  • @Eijin: If you want people to investigate your problme, you must provide a "Complete" example, meaning, you need to send .cpp files (including one with a main entry point), .h file, .ui file, ideally .pro file. Else it's almost impossible to help. – jpo38 Feb 25 '18 at 10:08
  • I added a full visual studio example. I wasn't successful in creating a Qt Creator project. – Eejin Feb 25 '18 at 13:46
  • Not sure, but I would try `dialog->layout()->setSizeConstraint(QLayout::SetMinimumSize);`. The default size constraint seems kinda weird, because it won't update the dialog's minimumSize() except for the very first time. That would be useless for your case. – Johannes Schaub - litb Feb 25 '18 at 13:59
  • I also see you have a designated widget that contains your tiles, but when you add your tile, you need to call `updateGeometry()` because the minimum size may have changed. `updateGeometry()` is important because the widget item that keeps your widget in the dialogs layout may otherwise cache the minimum size of your widget to avoid having to call it again. See https://code.woboq.org/data/symbol.html?root=../qt5/&ref=_ZN13QWidgetItemV219invalidateSizeCacheEv#uses – Johannes Schaub - litb Feb 25 '18 at 14:25
  • Doing those two things doesn't seem to have changed anything. What makes resizing the window snap it to the correct dimension? – Eejin Feb 25 '18 at 17:29
  • I tried to reproduce the issue on Linux using the code you provided, but everything seems fine. – Philipp Ludwig Feb 28 '18 at 18:42
  • I added a video showing the issue. – Eejin Feb 28 '18 at 20:16

2 Answers2

5

Great MCVE, exactly what's needed to easily investigate the issue.

Looks like this FlowLayout class was not designed to have it's minimum size change on user action. Layout gets updated 'by chance' by QWidget kernel when the window is moved.

I could make it work smartly by modifying FlowLayout::minimumSize() behaviour, here are the changes I did:

  • Added QSize minSize; attribute to FlowLayout class
  • Modifed FlowLayout::minimumSize() to simply return this attribute
  • Added a third parameter QSize* pMinSize to doLayout function. This will be used to update this minSize attribute
  • Modified doLayout to save computed size to pMinSize parameter if specified
  • Had FlowLayout::setGeometry pass minSize attribute to doLayout and invalidate the layout if min size changed

The layout then behaves as expected.

int FlowLayout::heightForWidth(int width) const {
    const int height = doLayout(QRect(0, 0, width, 0), true,NULL); // jpo38: set added parameter to NULL here
    return height;
}

void FlowLayout::setGeometry(const QRect &rect) {
    QLayout::setGeometry(rect);

    // jpo38: update minSize from here, force layout to consider it if it changed
    QSize oldSize = minSize;
    doLayout(rect, false,&minSize);
    if ( oldSize != minSize )
    {
        // force layout to consider new minimum size!
        invalidate();
    }
}

QSize FlowLayout::minimumSize() const {
    // jpo38: Simply return computed min size
    return minSize;
}

int FlowLayout::doLayout(const QRect &rect, bool testOnly,QSize* pMinSize) const {
    int left, top, right, bottom;
    getContentsMargins(&left, &top, &right, &bottom);
    QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
    int x = effectiveRect.x();
    int y = effectiveRect.y();
    int lineHeight = 0;

    // jpo38: store max X
    int maxX = 0;

    for (auto&& item : itemList) {
        QWidget *wid = item->widget();
        int spaceX = horizontalSpacing();
        if (spaceX == -1)
            spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
        int spaceY = verticalSpacing();
        if (spaceY == -1)
            spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
        int nextX = x + item->sizeHint().width() + spaceX;
        if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
            x = effectiveRect.x();
            y = y + lineHeight + spaceY;
            nextX = x + item->sizeHint().width() + spaceX;
            lineHeight = 0;
        }

        if (!testOnly)
            item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));

        // jpo38: update max X based on current position
        maxX = qMax( maxX, x + item->sizeHint().width() - rect.x() + left );

        x = nextX;
        lineHeight = qMax(lineHeight, item->sizeHint().height());
    }

    // jpo38: save height/width as max height/xidth in pMinSize is specified
    int height = y + lineHeight - rect.y() + bottom;
    if ( pMinSize )
    {
        pMinSize->setHeight( height );
        pMinSize->setWidth( maxX );
    }
    return height;
}
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • Hey jpo38, great work. It automatically increases the required size now which is great. Though now trying to downsize the windows causes issues. They don't correctly fill up vertical space anymore if you try to reduce the horizontal space. I have updated the main post with a new link to the updated solution. – Eejin Mar 01 '18 at 13:51
  • @Eejin: Now you see the problem was coming from `minimumSize` you'll probably have to work on it to have it behave exactly as expected, including downsizing situation (because minimumSize will definitely prevent the widegt from being downsized). – jpo38 Mar 01 '18 at 15:49
1

I was having the same exact issue (albeit on PySide2 rather than C++).

@jpo38's answer above did not work directly, but it un-stuck me by giving me a new approach.

What worked was storing the last geometry, and using that geometry's width to calculate the minimum height.

Here is an untested C++ implementation based on the code in jpo38's answer (I don't code much in C++ so apologies in advance if some syntax is wrong):

int FlowLayout::heightForWidth(int width) const {
    const int height = doLayout(QRect(0, 0, width, 0), true); 
    return height;
}

void FlowLayout::setGeometry(const QRect &rect) {
    QLayout::setGeometry(rect);

    // e-l: update lastSize from here
    lastSize = rect.size();
    doLayout(rect, false);
}

QSize FlowLayout::minimumSize() const {
    // e-l: Call heightForWidth from here, my doLayout is doing things a bit differently with regards to margins, so might have to add or not add the margins here to the height
    QSize size;
    for (const QLayoutItem *item : qAsConst(itemList))
        size = size.expandedTo(item->minimumSize());

    const QMargins margins = contentsMargins();
    size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
    size.setHeight(heightForWidth(qMax(lastSize.width(), size.width())));
    return size;
}

int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
    int left, top, right, bottom;
    getContentsMargins(&left, &top, &right, &bottom);
    QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
    int x = effectiveRect.x();
    int y = effectiveRect.y();
    int lineHeight = 0;

    for (auto&& item : itemList) {
        QWidget *wid = item->widget();
        int spaceX = horizontalSpacing();
        if (spaceX == -1)
            spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
        int spaceY = verticalSpacing();
        if (spaceY == -1)
            spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
        int nextX = x + item->sizeHint().width() + spaceX;
        if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
            x = effectiveRect.x();
            y = y + lineHeight + spaceY;
            nextX = x + item->sizeHint().width() + spaceX;
            lineHeight = 0;
        }

        if (!testOnly)
            item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));

        x = nextX;
        lineHeight = qMax(lineHeight, item->sizeHint().height());
    }

    int height = y + lineHeight - rect.y() + bottom;

    return height;
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Erwan Leroy
  • 160
  • 10