如何在PyQt5中实现带动态网格布局的QScrollArea?
你需要的效果可以通过Qt官方的QFlowLayout(流式布局)实现,该布局会自动根据可用宽度调整每行放置的控件数量,自动换行,窗口宽度变化时自动重排所有控件,完全匹配你要的文件资源管理器式排列逻辑。
修改后的完整代码如下:
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import QFileDialog # Qt官方流式布局实现 class FlowLayout(QtWidgets.QLayout): def __init__(self, parent=None, margin=0, spacing=-1): super(FlowLayout, self).__init__(parent) if parent is not None: self.setContentsMargins(margin, margin, margin, margin) self.setSpacing(spacing) self.itemList = [] def __del__(self): item = self.takeAt(0) while item: item = self.takeAt(0) def addItem(self, item): self.itemList.append(item) def count(self): return len(self.itemList) def itemAt(self, index): if 0 <= index < len(self.itemList): return self.itemList[index] return None def takeAt(self, index): if 0 <= index < len(self.itemList): return self.itemList.pop(index) return None def expandingDirections(self): return QtCore.Qt.Orientations(QtCore.Qt.Orientation(0)) def hasHeightForWidth(self): return True def heightForWidth(self, width): height = self.doLayout(QtCore.QRect(0, 0, width, 0), True) return height def setGeometry(self, rect): super(FlowLayout, self).setGeometry(rect) self.doLayout(rect, False) def sizeHint(self): return self.minimumSize() def minimumSize(self): size = QtCore.QSize() for item in self.itemList: size = size.expandedTo(item.minimumSize()) margin, _, _, _ = self.getContentsMargins() size += QtCore.QSize(2 * margin, 2 * margin) return size def doLayout(self, rect, testOnly): 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.spacing() + wid.style().layoutSpacing(QtWidgets.QSizePolicy.PushButton, QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Horizontal) spaceY = self.spacing() + wid.style().layoutSpacing(QtWidgets.QSizePolicy.PushButton, QtWidgets.QSizePolicy.PushButton, QtCore.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(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint())) x = nextX lineHeight = max(lineHeight, item.sizeHint().height()) return y + lineHeight - rect.y() + bottom class Ui_AutoCal(object): def setupUi(self, AutoCal): AutoCal.setObjectName("AutoCal") AutoCal.resize(513, 551) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(AutoCal.sizePolicy().hasHeightForWidth()) AutoCal.setSizePolicy(sizePolicy) AutoCal.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor)) AutoCal.setStyleSheet("*{\n" "background-color: rgb(54, 54, 54);\n" "color:rgb(255,255,255)\n" "}\n" "\n" "QPushButton\n" "{\n" "border-radius: 25px;\n" "min-width: 80px;\n" "min-height: 40px;\n" "}\n" "\n" "QLineEdit\n" "{\n" "color:rgb(0,0,0)\n" "}") self.centralwidget = QtWidgets.QWidget(AutoCal) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") self.groupBox = QtWidgets.QGroupBox(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) self.groupBox.setSizePolicy(sizePolicy) self.groupBox.setAlignment(QtCore.Qt.AlignCenter) self.groupBox.setObjectName("groupBox") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox) self.verticalLayout_6.setObjectName("verticalLayout_6") self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.label = QtWidgets.QLabel(self.groupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) self.label.setSizePolicy(sizePolicy) self.label.setObjectName("label") self.horizontalLayout_3.addWidget(self.label) self.lineEdit = QtWidgets.QLineEdit(self.groupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth()) self.lineEdit.setSizePolicy(sizePolicy) self.lineEdit.setObjectName("lineEdit") self.horizontalLayout_3.addWidget(self.lineEdit) self.pushButton = QtWidgets.QPushButton(self.groupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth()) self.pushButton.setSizePolicy(sizePolicy) self.pushButton.setObjectName("pushButton") # self.pushButton.clicked.connect(self.openFileNameDialog ) self.horizontalLayout_3.addWidget(self.pushButton) self.verticalLayout_6.addLayout(self.horizontalLayout_3) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.label_2 = QtWidgets.QLabel(self.groupBox) self.label_2.setObjectName("label_2") self.horizontalLayout_4.addWidget(self.label_2) self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox) self.lineEdit_2.setText("") self.lineEdit_2.setObjectName("lineEdit_2") self.horizontalLayout_4.addWidget(self.lineEdit_2) self.pushButton_2 = QtWidgets.QPushButton(self.groupBox) self.pushButton_2.setObjectName("pushButton_2") self.horizontalLayout_4.addWidget(self.pushButton_2) self.verticalLayout_6.addLayout(self.horizontalLayout_4) self.verticalLayout.addWidget(self.groupBox) self.verticalLayout_3 = QtWidgets.QVBoxLayout() self.verticalLayout_3.setObjectName("verticalLayout_3") self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget) self.groupBox_2.setEnabled(True) self.groupBox_2.setAlignment(QtCore.Qt.AlignCenter) self.groupBox_2.setObjectName("groupBox_2") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_2) self.verticalLayout_5.setObjectName("verticalLayout_5") self.scrollArea = QtWidgets.QScrollArea(self.groupBox_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.scrollAreaWidgetContents = QtWidgets.QWidget() # 替换原来的QGridLayout为流式布局 self.flowLayout = FlowLayout(self.scrollAreaWidgetContents, margin=10, spacing=10) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.fillScrollArea() self.verticalLayout_5.addWidget(self.scrollArea) self.verticalLayout_3.addWidget(self.groupBox_2) self.verticalLayout.addLayout(self.verticalLayout_3) AutoCal.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(AutoCal) self.menubar.setGeometry(QtCore.QRect(0, 0, 513, 22)) self.menubar.setObjectName("menubar") self.menuWorkSpace = QtWidgets.QMenu(self.menubar) self.menuWorkSpace.setObjectName("menuWorkSpace") self.menuRecipients = QtWidgets.QMenu(self.menubar) self.menuRecipients.setObjectName("menuRecipients") AutoCal.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(AutoCal) self.statusbar.setObjectName("statusbar") AutoCal.setStatusBar(self.statusbar) self.menubar.addAction(self.menuWorkSpace.menuAction()) self.menubar.addAction(self.menuRecipients.menuAction()) self.retranslateUi(AutoCal) QtCore.QMetaObject.connectSlotsByName(AutoCal) def retranslateUi(self, AutoCal): _translate = QtCore.QCoreApplication.translate AutoCal.setWindowTitle(_translate("AutoCal", "AutoCal")) self.groupBox.setTitle(_translate("AutoCal", "Create New WorkSpace")) self.label.setText(_translate("AutoCal", "Choose WorkSpace Directory")) self.pushButton.setText(_translate("AutoCal", "Browse")) self.label_2.setText(_translate("AutoCal", "WorkSpace Name")) self.pushButton_2.setText(_translate("AutoCal", "Create")) self.groupBox_2.setTitle(_translate("AutoCal", "Choose Existing WorkSpace")) self.menuWorkSpace.setTitle(_translate("AutoCal", "WorkSpace")) self.menuRecipients.setTitle(_translate("AutoCal", "Recipients")) def fillScrollArea(self): # 生成测试按钮,可根据实际需求替换为工作区按钮 for i in range(50): btn = QtWidgets.QPushButton(f"WorkSpace {i+1}") self.flowLayout.addWidget(btn) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) AutoCal = QtWidgets.QMainWindow() ui = Ui_AutoCal() ui.setupUi(AutoCal) AutoCal.show() sys.exit(app.exec_())
上述代码已经解决你提到的两个核心问题:
- 按钮默认设置了最小宽高,不会出现文字截断,所有按钮都完整展示在滚动区域内
- 流式布局会自动监听滚动区宽度变化,自动重排所有按钮,宽度变大时自动将下方按钮移到上方空位,宽度变小时自动换行,无需手动实现排列逻辑
内容的提问来源于stack exchange,提问作者ST C
相关产品推荐
相关产品推荐

