Qt Examples / Widgets / FlowLayout not working in MainWindow
-
FlowLayout implements a layout that handles different window sizes. The widget placement changes depending on the width of the application window.
The code is here:
https://code.qt.io/cgit/qt/qtbase.git/tree/examples/widgets/layouts/flowlayout?h=6.7
In the example, the "Window" class is a QWidget, and it is shown with "window.show()".
I just noticed that if I change the "Window" from QWidget to QMainWindow, the layout will stop working:
diff --git a/window.h b/window.h index fd4bf40..2f5ef22 100644 --- a/window.h +++ b/window.h @@ -4,13 +4,13 @@ #ifndef WINDOW_H #define WINDOW_H -#include <QWidget> +#include <QMainWindow> QT_BEGIN_NAMESPACE class QLabel; QT_END_NAMESPACE //! [0] -class Window : public QWidget +class Window : public QMainWindow { Q_OBJECT diff --git a/window.cpp b/window.cpp index e3f0ceb..f5991ef 100644 --- a/window.cpp +++ b/window.cpp @@ -8,6 +8,7 @@ //! [1] Window::Window() { + QWidget *centralWidget = new QWidget; FlowLayout *flowLayout = new FlowLayout; flowLayout->addWidget(new QPushButton(tr("Short"))); @@ -15,7 +16,8 @@ Window::Window() flowLayout->addWidget(new QPushButton(tr("Different text"))); flowLayout->addWidget(new QPushButton(tr("More text"))); flowLayout->addWidget(new QPushButton(tr("Even longer button text"))); - setLayout(flowLayout); + centralWidget->setLayout(flowLayout); + setCentralWidget(centralWidget); setWindowTitle(tr("Flow Layout")); }
What is the correct way to fix it?
-
Hi and welcome to devnet,
Use a central widget and apply the layout on it.-> misread the code.QMainWindow already has a layout to provide all its facilities. You likely have a warning printed in your application logs warning you about trying to replace it. -> Warning that you won't have since you are doing things correctly.
-
Hi and welcome to devnet,
Use a central widget and apply the layout on it.-> misread the code.QMainWindow already has a layout to provide all its facilities. You likely have a warning printed in your application logs warning you about trying to replace it. -> Warning that you won't have since you are doing things correctly.
@SGaist That's just what the patch did: create a new widget with the flow layout, and call QMainWindow::setCentralWidget.
There are no warnings in application logs.
More specifically, FlowLayout's minimum height is not working in MainWindow, the other functions are ok.
-
Oh sorry ! My bad ! I was too tired when I read the patch 😅
Which version of Qt are you using ?
What result are you getting with your changes ? -
I tested:
Window::Window() { QWidget *widget = new QWidget; FlowLayout *flowLayout = new FlowLayout(widget); flowLayout->addWidget(new QPushButton(tr("Short"))); flowLayout->addWidget(new QPushButton(tr("Longer"))); flowLayout->addWidget(new QPushButton(tr("Different text"))); flowLayout->addWidget(new QPushButton(tr("More text"))); flowLayout->addWidget(new QPushButton(tr("Even longer button text"))); setCentralWidget(widget); setWindowTitle(tr("Flow Layout")); }
Which is just a simpler variation on what you have and it's working correctly.
-
FlowLayout implements a layout that handles different window sizes. The widget placement changes depending on the width of the application window.
The code is here:
https://code.qt.io/cgit/qt/qtbase.git/tree/examples/widgets/layouts/flowlayout?h=6.7
In the example, the "Window" class is a QWidget, and it is shown with "window.show()".
I just noticed that if I change the "Window" from QWidget to QMainWindow, the layout will stop working:
diff --git a/window.h b/window.h index fd4bf40..2f5ef22 100644 --- a/window.h +++ b/window.h @@ -4,13 +4,13 @@ #ifndef WINDOW_H #define WINDOW_H -#include <QWidget> +#include <QMainWindow> QT_BEGIN_NAMESPACE class QLabel; QT_END_NAMESPACE //! [0] -class Window : public QWidget +class Window : public QMainWindow { Q_OBJECT diff --git a/window.cpp b/window.cpp index e3f0ceb..f5991ef 100644 --- a/window.cpp +++ b/window.cpp @@ -8,6 +8,7 @@ //! [1] Window::Window() { + QWidget *centralWidget = new QWidget; FlowLayout *flowLayout = new FlowLayout; flowLayout->addWidget(new QPushButton(tr("Short"))); @@ -15,7 +16,8 @@ Window::Window() flowLayout->addWidget(new QPushButton(tr("Different text"))); flowLayout->addWidget(new QPushButton(tr("More text"))); flowLayout->addWidget(new QPushButton(tr("Even longer button text"))); - setLayout(flowLayout); + centralWidget->setLayout(flowLayout); + setCentralWidget(centralWidget); setWindowTitle(tr("Flow Layout")); }
What is the correct way to fix it?
@F32_ said in Qt Examples / Widgets / FlowLayout not working in MainWindow:
I just noticed that if I change the "Window" from QWidget to QMainWindow, the layout will stop working:
Further to @SGaist, I tried exactly your own code. For me it worked fine, just like if you use
QWidget
rather thanQMainWindow
. Ubuntu 24.04, xcb, Qt 6.4.2. -
Oh sorry ! My bad ! I was too tired when I read the patch 😅
Which version of Qt are you using ?
What result are you getting with your changes ? -
@F32_
I don't understand why what you are showing is wrong in your screenshot? Looks fine to me. 3rd button is "long" and does not fit on first line, so wrapped to second line. Same for final button. Yes, if buttons do not all fit vertically they will go off the bottom of what is visible. What else do you expect? Do you expect it to resize the widget vertically to fit them all? I don't think it's intended to do that, just the wrapping. And I see exactly the same whether it'sQMainWindow
orQWidget
. Show screenshots of both cases if you are claiming they differ.FWIW, I did use that flowlayout code a few years ago. And I did find a "bug" in it, and had to change the code to make it "right". I suspect I may have posted in this forum saying what was wrong and what I did, but I don't recall after all this time. I don't know whether what I changed would have any relationship to whatever your issue is. I will have a look around to see if I can find it, but unless I post here shortly assume I cannot locate it.
-
@F32_
OK, I have located my use of flowlayout in an old Python project I wrote, when I was using that for Qt initially. I paste it below.Note I wrote there
# https://code.qt.io/cgit/qt/qtbase.git/tree/examples/widgets/layouts/flowlayout/flowlayout.cpp?h=5.13 # Modified/adapted by jon, 10/07/2019, to translate into Python/PyQt
I have a feeling the "adapted" indicates I made some change. I would guess inside
def doLayout()
. Most lines should be a direct translation of the C++ source to Python. See if you can spot some "difference" from the corresponding C++ in that function?############################################################################# # # This file taken from # https://code.qt.io/cgit/qt/qtbase.git/tree/examples/widgets/layouts/flowlayout/flowlayout.cpp?h=5.13 # Modified/adapted by jon, 10/07/2019, to translate into Python/PyQt # # Copyright (C) 2016 The Qt Company Ltd. # Contact: https://www.qt.io/licensing/ # # This file is part of the examples of the Qt Toolkit. # # $QT_BEGIN_LICENSE:BSD$ # Commercial License Usage # Licensees holding valid commercial Qt licenses may use this file in # accordance with the commercial license agreement provided with the # Software or, alternatively, in accordance with the terms contained in # a written agreement between you and The Qt Company. For licensing terms # and conditions see https://www.qt.io/terms-conditions. For further # information use the contact form at https://www.qt.io/contact-us. # # BSD License Usage # Alternatively, you may use this file under the terms of the BSD license # as follows: # # "Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # * Neither the name of The Qt Company Ltd nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." # # $QT_END_LICENSE$ # ############################################################################# import typing from PyQt5.QtCore import Qt, QPoint, QRect, QSize from PyQt5.QtWidgets import QWidget, QLayout, QLayoutItem, QStyle, QSizePolicy class FlowLayout(QLayout): def __init__(self, parent: QWidget=None, margin: int=-1, hSpacing: int=-1, vSpacing: int=-1): super().__init__(parent) self.itemList = list() self.m_hSpace = hSpacing self.m_vSpace = vSpacing self.setContentsMargins(margin, margin, margin, margin) def __del__(self): # copied for consistency, not sure this is needed or ever called item = self.takeAt(0) while item: item = self.takeAt(0) def addItem(self, item: QLayoutItem): self.itemList.append(item) def horizontalSpacing(self) -> int: if self.m_hSpace >= 0: return self.m_hSpace else: return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing) def verticalSpacing(self) -> int: if self.m_vSpace >= 0: return self.m_vSpace else: return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing) def count(self) -> int: return len(self.itemList) def itemAt(self, index: int) -> typing.Union[QLayoutItem, None]: if 0 <= index < len(self.itemList): return self.itemList[index] else: return None def takeAt(self, index: int) -> typing.Union[QLayoutItem, None]: if 0 <= index < len(self.itemList): return self.itemList.pop(index) else: return None def expandingDirections(self) -> Qt.Orientations: return Qt.Orientations(Qt.Orientation(0)) def hasHeightForWidth(self) -> bool: return True def heightForWidth(self, width: int) -> int: height = self.doLayout(QRect(0, 0, width, 0), True) return height def setGeometry(self, rect: QRect) -> None: super().setGeometry(rect) self.doLayout(rect, False) def sizeHint(self) -> QSize: return self.minimumSize() def minimumSize(self) -> QSize: size = QSize() for item in self.itemList: size = size.expandedTo(item.minimumSize()) margins = self.contentsMargins() size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom()) return size def smartSpacing(self, pm: QStyle.PixelMetric) -> int: parent = self.parent() if not parent: return -1 elif parent.isWidgetType(): return parent.style().pixelMetric(pm, None, parent) else: return parent.spacing() def doLayout(self, rect: QRect, testOnly: bool) -> int: left, top, right, bottom = self.getContentsMargins() effectiveRect = rect.adjusted(+left, +top, -right, -bottom) x = effectiveRect.x() y = effectiveRect.y() lineHeight = 0 for item in self.itemList: wid = item.widget() spaceX = self.horizontalSpacing() if spaceX == -1: spaceX = wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal) spaceY = self.verticalSpacing() if spaceY == -1: spaceY = wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical) nextX = x + item.sizeHint().width() + spaceX if nextX - spaceX > effectiveRect.right() and lineHeight > 0: x = effectiveRect.x() y = y + lineHeight + spaceY nextX = x + item.sizeHint().width() + spaceX lineHeight = 0 if not testOnly: item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) x = nextX lineHeight = max(lineHeight, item.sizeHint().height()) return y + lineHeight - rect.y() + bottom
-
@F32_
I don't understand why what you are showing is wrong in your screenshot? Looks fine to me. 3rd button is "long" and does not fit on first line, so wrapped to second line. Same for final button. Yes, if buttons do not all fit vertically they will go off the bottom of what is visible. What else do you expect? Do you expect it to resize the widget vertically to fit them all? I don't think it's intended to do that, just the wrapping. And I see exactly the same whether it'sQMainWindow
orQWidget
. Show screenshots of both cases if you are claiming they differ.FWIW, I did use that flowlayout code a few years ago. And I did find a "bug" in it, and had to change the code to make it "right". I suspect I may have posted in this forum saying what was wrong and what I did, but I don't recall after all this time. I don't know whether what I changed would have any relationship to whatever your issue is. I will have a look around to see if I can find it, but unless I post here shortly assume I cannot locate it.
@JonB In the original example, FlowLayout is in "Window" and "Window" is a QWidget.
Under Windows 11 and Qt 6.7.3, if the user attempts to resize the window and make it shorter, the action will be blocked by the value of minimumSize(), and the user will find that the window has a minimum height.
In the screenshot pasted in #7, the user will find that the minimum height is 3 rows. This feature is provided by:
//! [7] bool FlowLayout::hasHeightForWidth() const { return true; } int FlowLayout::heightForWidth(int width) const { int height = doLayout(QRect(0, 0, width, 0), true); return height; } //! [7]
If the user changes "Window" from QWidget to QMainWindow, this feature disappears, and the window's minimum height is always one row. The extra buttons overflow.
-
@JonB In the original example, FlowLayout is in "Window" and "Window" is a QWidget.
Under Windows 11 and Qt 6.7.3, if the user attempts to resize the window and make it shorter, the action will be blocked by the value of minimumSize(), and the user will find that the window has a minimum height.
In the screenshot pasted in #7, the user will find that the minimum height is 3 rows. This feature is provided by:
//! [7] bool FlowLayout::hasHeightForWidth() const { return true; } int FlowLayout::heightForWidth(int width) const { int height = doLayout(QRect(0, 0, width, 0), true); return height; } //! [7]
If the user changes "Window" from QWidget to QMainWindow, this feature disappears, and the window's minimum height is always one row. The extra buttons overflow.
@F32_ said in Qt Examples / Widgets / FlowLayout not working in MainWindow:
@JonB In the original example, FlowLayout is in "Window" and "Window" is a QWidget.
I know this. I do not understand what/why you are trying to say here.
If the user changes "Window" from QWidget to QMainWindow, this feature disappears, and the window's minimum height is always one row. The extra buttons overflow.
As I wrote, I have run your code where
Window
is either a plainQWidget
or aQMainWindow
. And I cannot see any difference in behaviour between the two. In both cases I can shrink the "window" vertically to minimum height, and that allows for the first line of pushbutton(s) plus I can just see the top line of the second row. I do not see either giving any minimum height of "3 rows", they both reduce to one row plus a tiny bit, as I wrote. If you see otherwise I suggested you show two screenshots, one withQMainWindow
and the other withQWidget
, so that we can see what you are seeing. I repeat again: if your first screenshot shows a minimum height where it is aQWidget
and you cannot reduce its height any further then I do not see that. Bear in mind my platform and Qt version. I cannot say any more than that.I don't know whether you can reproduce whatever your issue is without involving the flow layout at all? Maybe you are seeing some difference for minimal height between a
QWidget
vs aQMainWindow
regardless, and it's some issue about that? -
@F32_ said in Qt Examples / Widgets / FlowLayout not working in MainWindow:
@JonB In the original example, FlowLayout is in "Window" and "Window" is a QWidget.
I know this. I do not understand what/why you are trying to say here.
If the user changes "Window" from QWidget to QMainWindow, this feature disappears, and the window's minimum height is always one row. The extra buttons overflow.
As I wrote, I have run your code where
Window
is either a plainQWidget
or aQMainWindow
. And I cannot see any difference in behaviour between the two. In both cases I can shrink the "window" vertically to minimum height, and that allows for the first line of pushbutton(s) plus I can just see the top line of the second row. I do not see either giving any minimum height of "3 rows", they both reduce to one row plus a tiny bit, as I wrote. If you see otherwise I suggested you show two screenshots, one withQMainWindow
and the other withQWidget
, so that we can see what you are seeing. I repeat again: if your first screenshot shows a minimum height where it is aQWidget
and you cannot reduce its height any further then I do not see that. Bear in mind my platform and Qt version. I cannot say any more than that.I don't know whether you can reproduce whatever your issue is without involving the flow layout at all? Maybe you are seeing some difference for minimal height between a
QWidget
vs aQMainWindow
regardless, and it's some issue about that?@JonB I managed to create two gif files, which should be helpful to the community.
The original version (Qt 6.7.3, C++, Windows 11):
I believe this is the original design because every layout should be able to claim a minimal size.
The patched version (Qt 6.7.3, C++, Windows 11, with QMainWindow):
-
@JonB I managed to create two gif files, which should be helpful to the community.
The original version (Qt 6.7.3, C++, Windows 11):
I believe this is the original design because every layout should be able to claim a minimal size.
The patched version (Qt 6.7.3, C++, Windows 11, with QMainWindow):
@F32_
To be clear for others: with Qt 6.4.2 under Ubuntu/GNOME/xcb, I am claiming bothQWidget
&QMainWindow
cases are as per the second screenshot above. I never see a minimum height of 3 lines. Which of the two is the "correct" behaviour in the OP's case I do not claim to know :)