当前位置: 首页 > news >正文

Qt小组件 - 2(布局)瀑布流布局,GridLayout,FlowLayout

分享两个布局

FlowLayout

流式布局,从左到右排序

# coding:utf-8
from typing import Listfrom PySide6.QtCore import QSize, QPoint, Qt, QRect, QPropertyAnimation, QParallelAnimationGroup, QEasingCurve, QEvent, QTimer, QObject
from PySide6.QtWidgets import QLayout, QWidgetItem, QLayoutItemclass FlowLayout(QLayout):""" Flow layout """def __init__(self, parent=None, needAni=False, isTight=False):"""Parameters----------parent:parent window or layoutneedAni: boolwhether to add moving animationisTight: boolwhether to use the tight layout when widgets are hidden"""super().__init__(parent)self._items = []    # type: List[QLayoutItem]self._anis = []    # type: List[QPropertyAnimation]self._aniGroup = QParallelAnimationGroup(self)self._verticalSpacing = 10self._horizontalSpacing = 10self.duration = 300self.ease = QEasingCurve.Linearself.needAni = needAniself.isTight = isTightself._deBounceTimer = QTimer(self)self._deBounceTimer.setSingleShot(True)self._deBounceTimer.timeout.connect(lambda: self._doLayout(self.geometry(), True))self._wParent = Noneself._isInstalledEventFilter = Falsedef addItem(self, item):self._items.append(item)def insertItem(self, index, item):self._items.insert(index, item)def addWidget(self, w):super().addWidget(w)self._onWidgetAdded(w)def insertWidget(self, index, w):self.insertItem(index, QWidgetItem(w))self.addChildWidget(w)self._onWidgetAdded(w, index)def _onWidgetAdded(self, w, index=-1):if not self._isInstalledEventFilter:if w.parent():self._wParent = w.parent()w.parent().installEventFilter(self)else:w.installEventFilter(self)if not self.needAni:returnani = QPropertyAnimation(w, b'geometry')ani.setEndValue(QRect(QPoint(0, 0), w.size()))ani.setDuration(self.duration)ani.setEasingCurve(self.ease)w.setProperty('flowAni', ani)self._aniGroup.addAnimation(ani)if index == -1:self._anis.append(ani)else:self._anis.insert(index, ani)def setAnimation(self, duration, ease=QEasingCurve.Linear):""" set the moving animationParameters----------duration: intthe duration of animation in millisecondsease: QEasingCurvethe easing curve of animation"""if not self.needAni:returnself.duration = durationself.ease = easefor ani in self._anis:ani.setDuration(duration)ani.setEasingCurve(ease)def count(self):return len(self._items)def itemAt(self, index: int):if 0 <= index < len(self._items):return self._items[index]return Nonedef takeAt(self, index: int):if 0 <= index < len(self._items):item = self._items[index]   # type: QLayoutItemani = item.widget().property('flowAni')if ani:self._anis.remove(ani)self._aniGroup.removeAnimation(ani)ani.deleteLater()return self._items.pop(index).widget()return Nonedef removeWidget(self, widget):for i, item in enumerate(self._items):if item.widget() is widget:return self.takeAt(i)def removeAllWidgets(self):""" remove all widgets from layout """while self._items:self.takeAt(0)def takeAllWidgets(self):""" remove all widgets from layout and delete them """while self._items:w = self.takeAt(0)if w:w.deleteLater()def expandingDirections(self):return Qt.Orientation(0)def hasHeightForWidth(self):return Truedef heightForWidth(self, width: int):""" get the minimal height according to width """return self._doLayout(QRect(0, 0, width, 0), False)def setGeometry(self, rect: QRect):super().setGeometry(rect)if self.needAni:self._deBounceTimer.start(80)else:self._doLayout(rect, True)def sizeHint(self):return self.minimumSize()def minimumSize(self):size = QSize()for item in self._items:size = size.expandedTo(item.minimumSize())m = self.contentsMargins()size += QSize(m.left()+m.right(), m.top()+m.bottom())return sizedef setVerticalSpacing(self, spacing: int):""" set vertical spacing between widgets """self._verticalSpacing = spacingdef verticalSpacing(self):""" get vertical spacing between widgets """return self._verticalSpacingdef setHorizontalSpacing(self, spacing: int):""" set horizontal spacing between widgets """self._horizontalSpacing = spacingdef horizontalSpacing(self):""" get horizontal spacing between widgets """return self._horizontalSpacingdef eventFilter(self, obj: QObject, event: QEvent) -> bool:if obj in [w.widget() for w in self._items] and event.type() == QEvent.Type.ParentChange:self._wParent = obj.parent()obj.parent().installEventFilter(self)self._isInstalledEventFilter = Trueif obj == self._wParent and event.type() == QEvent.Type.Show:self._doLayout(self.geometry(), True)self._isInstalledEventFilter = Truereturn super().eventFilter(obj, event)def _doLayout(self, rect: QRect, move: bool):""" adjust widgets position according to the window size """aniRestart = Falsemargin = self.contentsMargins()x = rect.x() + margin.left()y = rect.y() + margin.top()rowHeight = 0spaceX = self.horizontalSpacing()spaceY = self.verticalSpacing()for i, item in enumerate(self._items):if item.widget() and not item.widget().isVisible() and self.isTight:continuenextX = x + item.sizeHint().width() + spaceXif nextX - spaceX > rect.right() - margin.right() and rowHeight > 0:x = rect.x() + margin.left()y = y + rowHeight + spaceYnextX = x + item.sizeHint().width() + spaceXrowHeight = 0if move:target = QRect(QPoint(x, y), item.sizeHint())if not self.needAni:item.setGeometry(target)elif target != self._anis[i].endValue():self._anis[i].stop()self._anis[i].setEndValue(target)aniRestart = Truex = nextXrowHeight = max(rowHeight, item.sizeHint().height())if self.needAni and aniRestart:self._aniGroup.stop()self._aniGroup.start()return y + rowHeight + margin.bottom() - rect.y()

例子

from random import randintfrom PySide6.QtWidgets import QWidget, QPushButton, QApplication
from flowLayout import FlowLayoutclass MyWidget(QWidget):def __init__(self):super().__init__()self.flowLayout = FlowLayout(self)for i in range(20):btn = QPushButton(f"Button {i + 1}")btn.setMinimumWidth(randint(100, 300))self.flowLayout.addWidget(btn)if __name__ == '__main__':app = QApplication()w = MyWidget()w.show()app.exec()

在这里插入图片描述

WaterfallLayout

瀑布流布局,布局内部使用scaledToWidth函数来设置高度,如果不设置scaledToWidth函数,默认保持原有的控件比例进行拉伸,弥补QGridLayout无法拉伸控件高度的缺点,以及无法自适应列数,一个仿VUEGridLayout

# coding: utf-8
from pathlib import Pathfrom PySide6.QtCore import QRect, QPoint, QSize, Property
from PySide6.QtWidgets import QWidget, QScrollArea
from flowLayout import FlowLayoutclass WaterfallLayout(FlowLayout):def __init__(self, parent=None):super().__init__(parent, False, False)self._itemMinWidth = 200self._geometry = self.geometry()def setItemMinimumWidth(self, width: int):self._itemMinWidth = widthself._doLayout(self.geometry(), True)def getItemMinimumWidth(self):return self._itemMinWidthdef _doLayout(self, rect: QRect, move: bool):aniRestart = Falsemargin = self.contentsMargins()left = rect.x() + margin.left()top = rect.y() + margin.top()spaceX = self.horizontalSpacing()spaceY = self.verticalSpacing()availableWidth = rect.width() - left - margin.right()columns = max(1, (availableWidth + spaceX) // (self.itemMinimumWidth + spaceX))itemWidth = int((availableWidth - (columns - 1) * spaceX) / columns)columnHeights = [top] * columnsfor i, item in enumerate(self._items):if item.widget() and not item.widget().isVisible() and self.isTight:continuewidget = item.widget()if hasattr(widget, 'scaledToWidth'):widget.scaledToWidth(itemWidth)height = widget.height()else:than = widget.height() / widget.width()  # 宽高比height = int(itemWidth * than)column = min(columnHeights.index(min(columnHeights)), columns - 1)x = left + column * (itemWidth + spaceX)y = columnHeights[column]if move:target = QRect(QPoint(x, y), QSize(itemWidth, height))if not self.needAni:item.setGeometry(target)elif target != self._anis[i].endValue():self._anis[i].stop()self._anis[i].setEndValue(target)aniRestart = TruecolumnHeights[column] += height + spaceYif self.needAni and aniRestart:self._aniGroup.stop()self._aniGroup.start()return top + max(columnHeights) + margin.bottom() - rect.y()itemMinimumWidth = Property(int, getItemMinimumWidth, setItemMinimumWidth)

例子

ImageLabel可参考https://blog.csdn.net/weixin_54217201/article/details/149336017?spm=1011.2415.3001.5331

scaledToWidth的组件

# coding: utf-8
from pathlib import Pathfrom PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget, QScrollAreafrom components import ImageLabel
from waterfallLayout import WaterfallLayoutclass MyWidget(QScrollArea):def __init__(self, parent=None):super().__init__(parent)self.setWidget(QWidget())self.setWidgetResizable(True)self.flowLayout = WaterfallLayout(self.widget())self.flowLayout.setItemMinimumWidth(350)self.widget().setLayout(self.flowLayout)for file in list(Path(r'G:\手机\壁纸').glob('*.*'))[:5]:item = ImageLabel()item.setRadius(5)item.setIsCenter(True)item.setImage(file)item.setMinimumSize(QSize(300, 200))self.flowLayout.addWidget(item)if __name__ == '__main__':import sysfrom PySide6.QtWidgets import QApplicationapp = QApplication(sys.argv)w = MyWidget()w.resize(867, 628)w.show()sys.exit(app.exec())

在这里插入图片描述

没有 scaledToWidth组件

setItemMinimumWidth的值必须大于item.width(),需要设置setMinimumSize否则无法换行,找半天,但是没找到原因

from pathlib import Pathfrom PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget, QScrollAreafrom components import ImageLabel
from waterfallLayout import WaterfallLayoutclass MyWidget(QScrollArea):def __init__(self, parent=None):super().__init__(parent)self.setWidget(QWidget())self.setWidgetResizable(True)self.flowLayout = WaterfallLayout(self.widget())self.flowLayout.setItemMinimumWidth(350)self.widget().setLayout(self.flowLayout)for file in list(Path(r'G:\手机\壁纸').glob('*.*'))[:10]:item = QLabel()item.setPixmap(QPixmap(file))item.setMinimumSize(QSize(300, 200))item.setScaledContents(True)self.flowLayout.addWidget(item)if __name__ == '__main__':import sysfrom PySide6.QtWidgets import QApplicationapp = QApplication(sys.argv)w = MyWidget()w.resize(867, 628)w.show()sys.exit(app.exec())

没有设置scaledToWidth

http://www.dtcms.com/a/278648.html

相关文章:

  • QT跨平台应用程序开发框架(7)—— 常用输入类控件
  • [Dify] -基础入门10- Dify 应用开发与 ChatGPT 的区别与优势分析
  • Sharding-Sphere学习专题(四)广播表和绑定表、分片审计
  • 【王树森推荐系统】物品冷启05:流量调控
  • ether.js—6—contractFactory以部署ERC20代币标准为例子
  • 设备树知识点
  • OneCode3.0 MCPServer:注解驱动的AI原生服务架构与实践
  • Python量化交易一体化解决方案
  • GStreamer 详解
  • JavaScript学习第九章-第三部分(内建对象)
  • 注解(Annotation)
  • 数据分类分级和重要数据标准解读
  • iOS —— 网易云仿写
  • 微信小程序——配置路径别名和省略后缀
  • iOS App 安全加固全流程:静态 + 动态混淆对抗逆向攻击实录
  • iOS如何查看电池容量?理解系统限制与开发者级能耗调试方法
  • 纯C++11实现!零依赖贝叶斯情感分析系统,掌握机器学习系统工程化的秘密!
  • Android Studio C++/JNI/Kotlin 示例 三
  • 基于51单片机的贪吃蛇游戏Protues仿真设计
  • 图算法在前端的复杂交互
  • 网络编程(套接字)
  • AI数据分析仪设计原理图:RapidIO信号接入 平板AI数据分析仪
  • RPC vs RESTful架构选择背后的技术博弈
  • 从数据洞察到设计创新:UI前端如何利用数字孪生提升产品交互体验?
  • 数字孪生技术引领UI前端设计新潮流:智能交互界面的个性化定制
  • 【Linux网络编程】应用层自定义协议与序列化
  • React强大且灵活hooks库——ahooks入门实践之DOM类hook(dom)详解
  • Reactor 模式详解
  • 订单初版—6.生单链路实现的重构文档
  • Vue3 学习教程,从入门到精通,Vue 3 表单控件绑定详解与案例(7)