Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt Window incorrect size until user event

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.

like image 379
Eejin Avatar asked Feb 16 '18 17:02

Eejin


2 Answers

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;
}
like image 60
jpo38 Avatar answered Sep 29 '22 19:09

jpo38


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;
}
like image 37
Erwan Leroy Avatar answered Sep 29 '22 20:09

Erwan Leroy