(一)前言
原本是想用python实现一个内容展示区,具体内容是通过一张张小卡片来呈现的,其中包括增加卡片和删除卡片的功能。
基本展示框架的实现有两种方案:
-
方案一:使用ListView+委托(复用委托的paint方法)实现
-
方案二:使用一个QScrollWidget+frame实现,frame充当卡片
其中,方案一的实现由于用到了paint,所以卡片上的小部件是无法交互的,这也阻碍了后面代码功能的实现,遂放弃.方案二的实现似乎更简单暴力,但是在实现卡片添加和删除的时候,遇到了ScrollWidget(滚动区)的大小无法自适应的问题(不是单纯添加一句ScrollWidget.adjustSize()就可以解决的)。最终效果图:
这里面涉及到QT的事件处理机制。那下面我就为你一一道来。
(二)卡片展示区基本框架
from PySide6.QtCore import QRect, QSize, Qt, QTimer
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QPushButton,
QSizePolicy, QSpacerItem, QTextEdit, QVBoxLayout,
QWidget, QMainWindow, QScrollArea)
from PySide6 import QtCore
import icon
# 卡片
class Frame(QFrame):
# 初始化
def __init__(self,parent):
pass
# 卡片UI
def ui(self):
pass
# 绑定卡片部件的事件方法
def bind(self):
pass
# 添加卡片部件的样式方法
def setStyleSheet_user(self):
pass
# 删除一个卡片的方法
def delFrame(self):
pass
# 对卡片写入内容的方法
def setText(self,title,description):
pass
# 滑动区域
class Main(QMainWindow):
# 初始化
def __init__(self):
pass
# 滑动区域UI
def ui(self):
pass
# 添加一个卡片的方法
def addFrame(self):
pass
效果图:
(三)内部细节
简述: 这里主要聊卡片的删除功能「eg:添加功能同理」
1.初步实现
class Frame(QFrame):
...
def delFrame(self):
self.deleteLater()
self.parent.scrollWidget.adjustSize() # 更新ScrollArea的大小
...
「这样子会出现一个问题,删除frame后展示区的页面没有自动缩小,也就是scrollWidget.adjustSize()并没有生效」
经过一番折腾和猜想,我才找到了处理方法。
2.原因
- QT捕获到用户触发的事件后,会把触发事件包装成一个QEvent对象,接着会带着QEvent到eventFilters列表中查找对应的事件处理函数(也就是事件过滤器)
- 在事件到达之后, 事件过滤器以安装次序被调用。事件过滤器函数( eventFilter() ) 返回值是bool型, 如果返回true, 则表示事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理。如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理
- 如果学习过scrapy,你就会发现这个逻辑和scrapy的管道逻辑很像
- 在上面的实例中,我们把
self.deleteLater()
和self.parent.scrollWidget.adjustSize()
封装到delFrame
中,并把delFrame
绑定到了删除按键的clicked
事件中 - 当我点击后删除键后,qt会查找
clicked
对应的处理事件函数(事件过滤器),也就是delFrame
函数 - 因为
delFrame
事件中含有self.deleteLater()
和self.scrollWidget.adjustSize()
这两个事件(这两个事件的总称是一次事件循环的迭代),Qt同时会带着这两个事件到eventFilters列表中,寻找对应的事件过滤函数 - 无论是哪个函数先执行,最先完成的一定是
self.parent.scrollWidget.adjustSize()
,也就是说,滑动区域的大小调整一定是优先于卡片添加的,并没有达到更新大小的效果
附上几张删除卡片时的GIF,便于理解:
-
第一次调整:
- 可以发现,第一次调整前scrollWidget的大小是(1782,239),调整后的大小应该是(1605,239)。实际上,它的大小没有改变。
- 可以发现,第一次调整前scrollWidget的大小是(1782,239),调整后的大小应该是(1605,239)。实际上,它的大小没有改变。
-
第二次调整:
- 👆第二次调整前scrollWidget的大小是(1782,239),调整后的大小应该是(1428,239)。实际上,它的大小变成了(1605,239),改变的值永远是上一次scrollWidget的大小。
- 👆第二次调整前scrollWidget的大小是(1782,239),调整后的大小应该是(1428,239)。实际上,它的大小变成了(1605,239),改变的值永远是上一次scrollWidget的大小。
结论:
这就很好地说明了,当添加卡片事件和调整滑动区域大小调整事件绑定在同一个循环事件迭代中,最先完成的一定是滑动区域大小调整事件。所以,我们需要把这两个事件拆开,分发到不同的循环事件迭代中。
3.解决方案
把这两个事件拆开,分发到不同的循环事件迭代中,把调整大小事件放到下一次循环事件迭代中.
def delFrame(self):
self.deleteLater()
QTimer.singleShot(0, lambda: self.parent.scrollWidget.adjustSize())
(四)总结
如果对Qt事件还不是很清楚的话,可以参考这篇文章.
这里是Rev_RoastDuck,住在优快云.如果这篇文章对你有帮助,欢迎点赞+收藏+关注.
(五)仓库
Github: https://github.com/Rev-RoastedDuck/Animated-ListView
Github: https://github.com/Rev-RoastedDuck/Qt-RoastedDuck-Widgets