我想创建一个类似于KDE(或Gnome或MacOS)系统设置的小部件(例如,像这张图片)
我已经从Qt docs exampe实现了一个FlowLayout。
如果我将一些FlowLayout小部件(包含在带有QVBoxLayout的容器小部件中)放入QScrollArea并调整QSrollArea的大小,那么所有内容都会流动并重新布局。
然而,如果我增加滚动区域的宽度以使其需要更少的高度,滚动区域仍然认为其小部件需要 最小宽度的原始高度:
如何更新滚动区域及其子项的实际高度,以便在不再需要时垂直滚动条消失?
下面,您将找到FlowLayout的(Python)实现,并在__main__
块中找到实际示例。
干杯, 斯蒂芬
"""
PyQt5 port of the `layouts/flowlayout
<https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ example
from Qt5.
Usage:
python3 -m pip install pyqt5
python3 flow_layout.py
"""
from PyQt5.QtCore import pyqtSignal, QPoint, QRect, QSize, Qt
from PyQt5.QtWidgets import QLayout, QSizePolicy, QSpacerItem
class FlowLayout(QLayout):
"""A ``QLayout`` that aranges its child widgets horizontally and
vertically.
If enough horizontal space is available, it looks like an ``HBoxLayout``,
but if enough space is lacking, it automatically wraps its children into
multiple rows.
"""
heightChanged = pyqtSignal(int)
def __init__(self, parent=None, margin=0, spacing=-1):
super().__init__(parent)
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self._item_list = []
def __del__(self):
while self.count():
self.takeAt(0)
def addItem(self, item): # pylint: disable=invalid-name
self._item_list.append(item)
def addSpacing(self, size): # pylint: disable=invalid-name
self.addItem(QSpacerItem(size, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))
def count(self):
return len(self._item_list)
def itemAt(self, index): # pylint: disable=invalid-name
if 0 <= index < len(self._item_list):
return self._item_list[index]
return None
def takeAt(self, index): # pylint: disable=invalid-name
if 0 <= index < len(self._item_list):
return self._item_list.pop(index)
return None
def expandingDirections(self): # pylint: disable=invalid-name,no-self-use
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self): # pylint: disable=invalid-name,no-self-use
return True
def heightForWidth(self, width): # pylint: disable=invalid-name
height = self._do_layout(QRect(0, 0, width, 0), True)
return height
def setGeometry(self, rect): # pylint: disable=invalid-name
super().setGeometry(rect)
self._do_layout(rect, False)
def sizeHint(self): # pylint: disable=invalid-name
return self.minimumSize()
def minimumSize(self): # pylint: disable=invalid-name
size = QSize()
for item in self._item_list:
minsize = item.minimumSize()
extent = item.geometry().bottomRight()
size = size.expandedTo(QSize(minsize.width(), extent.y()))
margin = self.contentsMargins().left()
size += QSize(2 * margin, 2 * margin)
return size
def _do_layout(self, rect, test_only=False):
m = self.contentsMargins()
effective_rect = rect.adjusted(+m.left(), +m.top(), -m.right(), -m.bottom())
x = effective_rect.x()
y = effective_rect.y()
line_height = 0
for item in self._item_list:
wid = item.widget()
space_x = self.spacing()
space_y = self.spacing()
if wid is not None:
space_x += wid.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
space_y += wid.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
next_x = x + item.sizeHint().width() + space_x
if next_x - space_x > effective_rect.right() and line_height > 0:
x = effective_rect.x()
y = y + line_height + space_y
next_x = x + item.sizeHint().width() + space_x
line_height = 0
if not test_only:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = next_x
line_height = max(line_height, item.sizeHint().height())
new_height = y + line_height - rect.y()
self.heightChanged.emit(new_height)
return new_height
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QScrollArea, QVBoxLayout, QWidget
class Container(QWidget):
def __init__(self):
super().__init__()
self.setLayout(QVBoxLayout())
self._widgets = []
def sizeHint(self):
w = self.size().width()
h = 0
for widget in self._widgets:
h += widget.layout().heightForWidth(w)
sh = super().sizeHint()
print(sh)
print(w, h)
return sh
def add_widget(self, widget):
self._widgets.append(widget)
self.layout().addWidget(widget)
def add_stretch(self):
self.layout().addStretch()
app = QApplication(sys.argv) # pylint: disable=invalid-name
container = Container()
for i in range(2):
w = QWidget()
w.setWindowTitle('Flow Layout')
l = FlowLayout(w, 10)
w.setLayout(l)
l.addWidget(QPushButton('Short'))
l.addWidget(QPushButton('Longer'))
l.addWidget(QPushButton('Different text'))
l.addWidget(QPushButton('More text'))
l.addWidget(QPushButton('Even longer button text'))
container.add_widget(w)
container.add_stretch()
sa = QScrollArea()
sa.setWidgetResizable(True)
sa.setWidget(container)
sa.show()
sys.exit(app.exec_())
答案 0 :(得分:0)
解决方案(令人惊讶)简单:使用FlowLayout的heightChanged
信号来更新容器的最小高度(ScrollArea的小部件)。
这是一个有效的例子:
"""
PyQt5 port of the `layouts/flowlayout
<https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ example
from Qt5.
"""
from PyQt5.QtCore import pyqtSignal, QPoint, QRect, QSize, Qt
from PyQt5.QtWidgets import QLayout, QSizePolicy, QSpacerItem
class FlowLayout(QLayout):
"""A ``QLayout`` that aranges its child widgets horizontally and
vertically.
If enough horizontal space is available, it looks like an ``HBoxLayout``,
but if enough space is lacking, it automatically wraps its children into
multiple rows.
"""
heightChanged = pyqtSignal(int)
def __init__(self, parent=None, margin=0, spacing=-1):
super().__init__(parent)
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self._item_list = []
def __del__(self):
while self.count():
self.takeAt(0)
def addItem(self, item): # pylint: disable=invalid-name
self._item_list.append(item)
def addSpacing(self, size): # pylint: disable=invalid-name
self.addItem(QSpacerItem(size, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))
def count(self):
return len(self._item_list)
def itemAt(self, index): # pylint: disable=invalid-name
if 0 <= index < len(self._item_list):
return self._item_list[index]
return None
def takeAt(self, index): # pylint: disable=invalid-name
if 0 <= index < len(self._item_list):
return self._item_list.pop(index)
return None
def expandingDirections(self): # pylint: disable=invalid-name,no-self-use
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self): # pylint: disable=invalid-name,no-self-use
return True
def heightForWidth(self, width): # pylint: disable=invalid-name
height = self._do_layout(QRect(0, 0, width, 0), True)
return height
def setGeometry(self, rect): # pylint: disable=invalid-name
super().setGeometry(rect)
self._do_layout(rect, False)
def sizeHint(self): # pylint: disable=invalid-name
return self.minimumSize()
def minimumSize(self): # pylint: disable=invalid-name
size = QSize()
for item in self._item_list:
minsize = item.minimumSize()
extent = item.geometry().bottomRight()
size = size.expandedTo(QSize(minsize.width(), extent.y()))
margin = self.contentsMargins().left()
size += QSize(2 * margin, 2 * margin)
return size
def _do_layout(self, rect, test_only=False):
m = self.contentsMargins()
effective_rect = rect.adjusted(+m.left(), +m.top(), -m.right(), -m.bottom())
x = effective_rect.x()
y = effective_rect.y()
line_height = 0
for item in self._item_list:
wid = item.widget()
space_x = self.spacing()
space_y = self.spacing()
if wid is not None:
space_x += wid.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
space_y += wid.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
next_x = x + item.sizeHint().width() + space_x
if next_x - space_x > effective_rect.right() and line_height > 0:
x = effective_rect.x()
y = y + line_height + space_y
next_x = x + item.sizeHint().width() + space_x
line_height = 0
if not test_only:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = next_x
line_height = max(line_height, item.sizeHint().height())
new_height = y + line_height - rect.y()
self.heightChanged.emit(new_height)
return new_height
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QScrollArea, QVBoxLayout, QWidget, QGroupBox
app = QApplication(sys.argv)
container = QWidget()
container_layout = QVBoxLayout()
for i in range(2):
g = QGroupBox(f'Group {i}')
l = FlowLayout(margin=10)
l.heightChanged.connect(container.setMinimumHeight)
g.setLayout(l)
l.addWidget(QPushButton('Short'))
l.addWidget(QPushButton('Longer'))
l.addWidget(QPushButton('Different text'))
l.addWidget(QPushButton('More text'))
l.addWidget(QPushButton('Even longer button text'))
container_layout.addWidget(g)
container_layout.addStretch()
container.setLayout(container_layout)
w = QScrollArea()
w.setWindowTitle('Flow Layout')
w.setWidgetResizable(True)
w.setWidget(container)
w.show()
sys.exit(app.exec_())