7.QObject的删除操作
API函数 | 参数说明 | 返回值 | 功能作用 |
deleteLater(self) | None | None | 延迟销毁一个对象 |
前面说了,在PySide6的内存管理机制中,删除一个对象同时会解除其相关的父子关系,如果父对象被删除,子对象也会被删除(没有谁再指向子对象,子对象被回收)。deleteLater()方法的作用是不将对象立即删除,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。这样做的好处就是可以在这些延迟删除的时间内做一些事情,坏处在于内存释放不及时。
我们要注意其与del()之间的区别,见下例。
example:
# -*- coding:utf-8 -*-
from PySide6.QtCore import QObject
from PySide6.QtWidgets import QWidget,QApplication
import sys
class window(QWidget):
def __init__(self):
super(window, self).__init__()
self.resize(600,600)
self.move(300,300)
self.setWindowTitle("object信号断开测试")
obj1 = QObject()
obj2 = QObject()
obj3 = QObject()
obj3.setParent(obj2)
obj2.setParent(obj1)
obj1.setParent(self)
obj1.destroyed.connect(lambda :print("obj1被销毁了"))
obj2.destroyed.connect(lambda: print("obj2被销毁了"))
obj3.destroyed.connect(lambda: print("obj3被销毁了"))
del obj2
# obj2.deleteLater()
# print(obj1.children())
if __name__ == '__main__':
app = QApplication(sys.argv)
win = window()
win.show()
sys.exit(app.exec())
运行结果:
运行代码,命令行无显示。
既然我们说删除了对象就解除了对象与父对象之间的父子关系,那么删除了obj2,为什么没有触发相应的槽函数而输出"obj2被销毁了"?这是由于del()方法并没有奖obj2对象销毁,被删除的只是内存中存放obj2这个变量名。既然obj2对象并没有被删除,obj3也就不会被删除。
我们将del obj这行代码注释掉,打开obj.deletLater()和print(obj1.children())再运行,再窗口没有关掉的情况下输出结果如下:
[<PySide6.QtCore.QObject(0x1f0e3afc750) at 0x000001F0E3A795C0>]
obj2被销毁了
obj3被销毁了
可以看到,obj2此时被删除,同时触发槽函数,obj3由于失去了父对象,也在执行完后被删除。但是,我们发现最上面一行打印了一个对象。实际上这个对象就是obj2,是print(obj1.children())这行代码输出的结果。而且,我们还可以看出,上面我们说了deletLater()是稍后删除的特性,即如果是立即删除,输出中就不会显示obj2对象,且他的输出顺序应在最后面。所以,如果我们想在Pyside6中彻底删除一个对象,应当使用deleteLater()方法,而不是del()方法。
8.QObject内置简单定时器
在PySide6中,通常我们使用计时器类QTimer来用作一个定时装置,用来实现一些重复或者延迟操纵。比如,制作一个闹钟,定时到几点几分开始报警。或者间隔若干秒发送一个数据等。在QObject类为我们添加了一个便捷方法,可以不用创建QTimer对象,而进行简单的定时器启停操作。
API函数 | 参数说明 | 返回值 | 功能作用 |
startTimer(interval,timerType) | Interval:int Timertype: Qt.TimerType | id:int | 创建并启动一个定时器 |
killTimer(id) | id:int | None | 结束一个指定id的定时器 |
startTimer()方法被调用时会自动生成一个定时器对象,并返回定时器对象的id。也就是说通过startTimer()方法同时开启多个定时器。其参数Interval表示定时器触发一次的间隔时间,单位是毫秒,另个一参数Timertype是一个枚举类,表示定时间设置的时间精度。
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
Qt.TimerType | CoarseTimer | 0 | 粗精度定时器,只能精确到秒 |
PreciseTimer | 0 | 高精度定时器,精确到毫秒 | |
VeryCoarseTimer | 0 | 最低精度定时器,与预想值有5%的误差。 |
另外,我们创建和开启一个定时器不能啥都不干,而进行必要的操作,需要重写QObject的eventTimer()方法。这个事件的作用是,每当定时器时就会调用一次这个方法。
example:
class customObject(QObject):
def timerEvent(self, event):
super(customObject, self).timerEvent(event)
# 获取定时器的ID
timerID = event.timerId()
# 不同ID的定时器每隔设置时间打印一次当前时间
print("id为{}打印了时间{}".format(event.timerId(),datetime.now()))
class window(QWidget):
def __init__(self):
super(window, self).__init__()
self.resize(300,300)
self.setWindowTitle("object的定时器测试")
#这里需要创建两个实例属性,否则在self.stop方法中可能会报AttributeError
#这是由于startTimer()返回值之后调用后才能返回值。没有赋值,就不会有self.id1
#和self.id2这两个对象
self.id1 = None
self.id2 = None
self.obj = customObject()
#创建定时器的开始和停止按钮
start_btn = QPushButton("开始",self)
stop_btn = QPushButton("停止",self)
start_btn.clicked.connect(self.start)
start_btn.move(100,100)
stop_btn.clicked.connect(self.stop)
stop_btn.move(100,150)
def start(self):
self.id1 = self.obj.startTimer(2000, Qt.TimerType.VeryCoarseTimer)
self.id2 = self.obj.startTimer(5000, Qt.TimerType.CoarseTimer)
def stop(self):
if self.id1 != None:
self.obj.killTimer(self.id1)
self.obj.killTimer(self.id2)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = window()
win.show()
sys.exit(app.exec())
运行结果:
点击开始按钮运行一段时间,点击停止按钮。
id为1打印了时间2024-09-06 06:36:14.328256
id为1打印了时间2024-09-06 06:36:16.327776
id为2打印了时间2024-09-06 06:36:17.327489
id为1打印了时间2024-09-06 06:36:18.326969
id为1打印了时间2024-09-06 06:36:20.325268
id为2打印了时间2024-09-06 06:36:22.322949
id为1打印了时间2024-09-06 06:36:22.322949
id为1打印了时间2024-09-06 06:36:24.320156
id为1打印了时间2024-09-06 06:36:26.329585
id为2打印了时间2024-09-06 06:36:27.327152
需要说明的是,上例中的事件还没有学,只要知道它在本例中的作用即可,将在QWidget中简要介绍事件的用法,并在后面(信号与槽及事件)专篇中学习。
9.QTimer定时器类
由于本人是按照控件一个一个学习的,那么每个控件中遇到的非QWidgets模块中的类都会跟随学习,这样会将知识都联系起来。
1).QTimer介绍(官翻)
QTimer类为计时器提供了一个高级编程接口。要使用它,需要创建一个QTimer,将其timeout()信号连接到适当的插槽,然后调用start()。从那时起,它将以恒定的间隔发出timeout()信号。
1秒(1000毫秒)定时器的示例:
timer = QTimer(self)
timer.timeout.connect(lambda:print("hello world!"))
timer.start(1000)
在调用start()后,每秒钟调用一次update()槽函数。
你可以通过调用setSingleShot (true)将计时器设置为只启动一次。你也可以使用静态方法singleShot()函数在指定的时间间隔后调用一个槽函数。
QTimer.singleShot(200, self.updateCaption)
在多线程应用程序中,可以在任何具有事件循环的线程中使用QTimer。要从非gui线程启动事件循环,请使用exec()。Qt使用定时器的线程亲和性来确定哪个线程将发出timeout()信号。因此,必须在线程中启动和停止定时器;不可能从另一个线程启动计时器。
作为一种特殊情况,启动次数为0的QTimer将立即启动,尽管0定时器与其他事件源之间的顺序没有指定。但0定时器可以用来完成一些工作,同时仍然一个简洁的用户界面予以支持。
timer = QTimer(self)
timer.timeout.connect(self.processOneThing)
timer.start()
start()方法被调用后,processOneThing()将被反复调用。它应该被编写成总是立即返回的方式(通常是在处理完一个数据项之后),这样Qt就可以将事件发送到用户界面,并在完成所有工作后立即停止定时器。这是在GUI应用程序中实现繁重工作的传统方式,但随着多线程在越来越多的平台上变得可用,我们希望0毫秒QTimer对象将逐渐被QThread s取代。(说人话就是别管0定时器是啥,多线程请用QThread类)。
- 精度和定时器分辨率
定时器的准确性依赖于底层操作系统和硬件。大多数平台支持1毫秒的分辨率,尽管计时器的精度在许多现实世界的情况下并不等于这个分辨率。
精度还取决于定时器的类型。对于PreciseTimer, QTimer将尝试保持1毫秒的精度。精确定时器永远不会比预期提前超时。
对于CoarseTimer和VeryCoarseTimer类型,QTimer可能比预期更早唤醒,在这些类型的间隔内:CoarseTimer为间隔的5%,而VeryCoarseTimer为间隔的500毫秒。
如果系统繁忙或无法提供所要求的精度,所有定时器类型都可能比预期晚启动。在这种超时启动的情况下,Qt只会触发一次timeout()。发射信号后,计时器将恢复其原始的间隔时间。
- QTimer的替代方案
使用startTimer()和timerEvent():调用对象的startTimer(),并在类中重写timerEvent()事件处理器(该类必须继承QObject)。缺点是timerEvent()不支持如单次定时器或信号等高级功能。
使用QBasicTimer:通常比直接使用startTimer()更方便。
概述三种方法,参见Timers。注意,某些操作系统限制可使用的定时器数量,Qt尝试解决这些限制12。
2).QTimer相关方法
API函数 | 参数说明 | 返回值 | 功能作用 |
QTimer(parent) | parent:QObject | None | 创建一个定时器 |
interval(self) | None | int | 获取定时器设置的启动间隔时间 |
isActive(self) | None | bool | 定时正在运行时返回True,否则,返回Flase |
isSingleShot(self) | None | bool | 获取定时器是否只启动一次。 |
remainingTime(self) | None | int | 获取定时器发射信号的剩余时间。如果定时器未运行,返回-1. |
setInterval(self, msec:) | msec: int | None | 设置定期启动的时间间隔 |
setSingleShot(self, singleShot) | singleShot: bool | None | 设置定时器是否为单次出发。默认值为Flase。 |
setTimerType(self, atype) | atype: PySide6.QtCore.Qt.TimerType | None | 设置定时器精度 |
singleShot(msec, context) | msec: int, context: PySide6.QtCore.QObject, functor: Callable | None | 创建并开始一个单此出发的定时器 |
singleShot(msec, functor) | msec: int, functor: Callable | None | 创建并开始一个单此出发的定时器 |
singleShot(msec, receiver, member) | msec: int, receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview] | None | 创建并开始一个单此出发的定时器 |
singleShot(msec, timerType, receiver, member) | msec: int, timerType: PySide6.QtCore.Qt.TimerType, receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview] | None | 创建并开始一个单此出发的定时器 |
start(self) | None | None | 启动定时器 |
start(self, msec: int) | msec | None | 间隔msec后,启动定时器 |
stop(self) | None | None | 停止定时器 |
timerId(self) | None | int | 获取定时器ID |
timerType(self) | None | Qt.TimerType | 获取定时器精度类型 |
setInterval()方法是设置定时器的启动间隔时间,参数单位为毫秒。默认情况下参数为0,也就是前面介绍中所说的0定时器,0定时器会立即启动。需要说明的是,当适应setInterval()设置定时器的时间间隔,会改变定时器的timerID。
另外,如果设置了setInterval(msc),点击start()后,定时器已经开始运行了,但是第一次发射信号或是调用TimerEvent()方法是从start()开始间隔msc后才进行的。
remainingTime()则是返回一个时间值,这个值是从调用remainingTime()到定时器最近的下一次发射信号时的差值。
singleShot()是一个静态方法,即可以直接用类来调用,而不用创建实例对象(如:QTimer.singleShot())。它有几个重载函数,这里将几个重载函数的参数介绍一下:mesc是调用方法后到发射信号的间隔时间; receiver是事件接收者,通常是QObject或其子类;member则是时间接受者的成员函数;context表示当前类或者实例对象中的一个成员函数; timerType是定时器精度。
QTimer在多线程中运用比较多,其在多线程中的注意事项主要是正确的将其销毁,防止内存泄漏,这部分内容将在多线程编程中学习。
3).QTimer事件
API函数 | 参数说明 | 返回值 | 功能作用 |
timerEvent(self, arg__1) | arg__1: PySide6.QtCore.QTimerEvent | None | 定时器事件 |
4).QTimer信号
信号 | 参数说明 | 返回值 | 功能作用 |
timeout() | None | None | 定时器设置的时间间隔到期时发射此信号 |
example:
# -*- coding:utf-8 -*-
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QWidget,QApplication,QPushButton
import sys
from datetime import datetime
class window(QWidget):
def __init__(self):
super(window, self).__init__()
self.resize(300,300)
self.setWindowTitle("object的定时器测试")
# 创建一个定时器
self.timer = QTimer(self)
self.timer.setInterval(5000)
#创建定时器的开始、停止和查看启动剩余时间按钮
self.start_btn = QPushButton("开始",self)
self.stop_btn = QPushButton("停止",self)
self.remaining_btn = QPushButton("剩余时间",self)
# 按钮连接到槽函数
self.start_btn.clicked.connect(self.start)
self.start_btn.move(100,100)
self.stop_btn.clicked.connect(self.stop)
self.stop_btn.move(100,150)
self.remaining_btn.clicked.connect(self.remaining)
self.remaining_btn.move(100,200)
self.timer.timeout.connect(lambda :print(datetime.now()))
# 静态方法再创建一个单词触发定时器,程序开始运行5s后调用start_btn按钮的click()
# 模拟点击click()
QTimer.singleShot(5000,self.start_btn,self.start_btn.click())
def start(self):
# 点击按钮定时器启动
self.timer.start()
def stop(self):
# 点击按钮定时器停止
self.timer.stop()
def remaining(self):
# 点击按钮打印启动剩余时间
print(self.timer.remainingTime())
if __name__ == '__main__':
app = QApplication(sys.argv)
win = window()
win.show()
sys.exit(app.exec())
10.QObject的事件
本小节仅列出QObject事件的相关API和作用,供以后查看。具体将在事件处理机制章节中详细介绍。
API函数 | 参数说明 | 返回值 | 功能作用 |
event(ev) | ev:QEvent | bool | 接受一个事件进行处理 |
childEvent(ev) | ev:QChildEvent | None | 接受并处理子类事件 |
customEvent(ev) | ev:QEvent | None | 自定义事件 |
eventFilter(watched,event) | Wathched:QObject被安装过滤器对象 Event:需要过滤的事件 | None | 如果对象被安装过滤器,则过滤事件event。如果在子类中重写此函数,希望过滤则返回true,否则返回Flase。 |
installEventFilter(obj) | Obj:QObject希望安装过滤器的对象 | None | 为一个对象安装过滤器 |
removeEventFilter(obj) | Obj:QObject希望移除过滤器的对象 | None | 移除一个已安装过滤的对象上的过滤器 |
timerEvent(ev) | ev:QTimerEvent | Int:定时器id | 接受并处理定时器事件11 |
11.QObject多线程
Pyside6的多线程通常使用QThread类,在后面的章节我们将仔细介绍。这里仅列出QObject与多线程的相关API,供查阅。
API函数 | 参数说明 | 返回值 | 功能作用 |
thread() | None | QThread | 返回对象所在的线程 |
moveToThread(thread) | Thread:QThread | None | 更改对象及其子对象的线程相关性。如果对象有父对象,则无法更改 |