赞
踩
从上图列出的所有基类可以看到,QObject 是所有的 Qt 对象的基类。
mro:Method Resolution Order,方法解析顺序,可以打印出来类的继承顺序,也有博主提出不需要调用的解决办法。
def setup_ui(self):
self.test_inheritance()
def test_inheritance(self):
mros = QObject.mro()
for mro in mros:
print(mro)
运行结果:可以看到Qobject(pyqt 的基类)也是继承自 object (python 的基类) 。
setObjectName(“唯一名称”):给一个 Qt 对象设置一个名称,一般这个名称是唯一的,当做对象的 ID 来使用。
objectName():获取一个 Qt 对象的名称。
setProperty(“属性名称”,值):给一个 Qt 对象动态的添加一个属性与值。
property(“属性名称”):获取一个对象的属性值。
dynamicPropertyNames():获取一个对象中所有通过 setProperty() 设置的属性名称。
# 存放所有子控件以及子控件的配置操作 def setup_ui(self): # self.test_inheritance() self.nameandAttribute() def nameandAttribute(self): # 测试API obj = QObject() obj.setObjectName("notice") print(obj.objectName()) obj.setProperty("level1", "error") obj.setProperty("level2", "warning") print(obj.property("level1")) print(obj.dynamicPropertyNames())
运行结果:
要求:
凡是提示的 QLabel 控件,都要求设置:
字体大小为 20px;
字体颜色为灰色;
边框圆角为 8px。
信息提示分多个级别:
正常 (normal)——绿色边框、绿色字体。
警告 (warning)——黄色边框、黄色字体。
错误 (error)——红色边框、红色字体。
涉及知识点:
先介绍一下 qss:qss 和控件的关系类似于前端 css 和 html 元素的关系。
样式相关:
def exmpleDemonstration(self):
label = QLabel(self)
label.setText("标签样式啊样式")
label.setStyleSheet("font-size: 30px; color: purple")
其中 “font-size: 30px; color: purple” 这个字符串就是用来设置标签的样式,为了开发方便,常常把字符串放到某个文件中,需要用的时候读取文件,作用到整个应用程序上,这个文件的后缀就是.qss (qss 我猜是样式表 Qt Style Sheet 的缩写)。
qss 内容:
QLabel{
background-color:yellow;
font-size: 30px;
color: black;
}
QPushButton{
background-color:red;
}
因为 qss 里面有很多样式,需要做一个选择器,很多个样式去匹配存在的控件。上面我用的是类选择器,还有很多其他选择器比如:通配符选择器、ID 选择器等等。
PyQt5 代码:
def exmpleDemonstration(self):
with open("style.qss", "r") as f:
app.setStyleSheet(f.read())
label = QLabel(self)
label.setText("标签样式啊样式")
label2 = QLabel(self)
label2.setText("第二个标签")
label2.move(100, 50)
button = QPushButton(self)
button.setText("按钮")
button.move(200, 200)
运行效果:
从运行效果可以看到,实现了从 style.qss 样式表中读取字符串,并在整个应用程序上实现,两个标签都变成了一种格式。
然后现在又出现了一个问题:如果只想使用标签怎么办,以上方法会一把子把所有的样式都给改了,这时候就要用到 ID 选择器(# ID 名称),这里的 ID 指的是前面说的 ObjectName。
设置如下:
label = QLabel(self)
label.setObjectName("notice")
label.setText("标签样式啊样式")
label2 = QLabel(self)
label.setObjectName("notice")
label2.setText("第二个标签")
label2.move(100, 50)
label3 = QLabel(self)
label3.setText("想要普通显示的部分")
label3.move(400, 50)
QLabel#notice{
background-color:yellow;
font-size: 30px;
color: black;
}
测试结果:
可以看到只有对象名称为 notice 的才会被修改样式,label3 还是默认样式。
解决了这一个问题,还有问题是如何给 label 和 label2 设置不同样式,因为现实生活中同样是 notice,有的提示正确信息,有的提示错误信息,还需要样式区分,这时候需要属性选择器。
qss 设置:
其中 border 设定边框为 1 像素宽,实线,颜色使用 gray 来表达。radius 表示半径,也就是圆角。
px 表示像素(Pixel),是相对电脑分辨率谈的长度单位,和实际无关。
QLabel#notice { font-size: 20px; color: gray; border: 1px solid gray; border-radius: 8px; } QLabel#notice[noticelevel="normal"] { color: green; border-color: green; } QLabel#notice[noticelevel="warning"] { color: yellow; border-color: yellow; } QLabel#notice[noticelevel="error"] { color: red; border-color: red; }
python 代码:
label = QLabel(self) label.setObjectName("notice") label.setText("没有任何地位的通知") label2 = QLabel(self) label2.setObjectName("notice") label2.setProperty("noticelevel", "warning") label2.setText("效率变低啦") label2.move(100, 50) button = QPushButton(self) button.setText("默默路过的按钮") button.move(200, 200) label3 = QLabel(self) label3.setObjectName("notice") label3.setProperty("noticelevel", "error") label3.setText("被退学了") label3.move(400, 50) label3 = QLabel(self) label3.setObjectName("notice") label3.setProperty("noticelevel", "normal") label3.setText("无事发生") label3.move(50, 300)
noticelevel 是属性名,normal、warning、error是属性的值。
实现效果:
到这儿就基本实现了案例的要求。
setParent(parent):设置父对象,父对象只能设置一个,设置多个会被新设置的给覆盖掉。给谁设置父对象,就调用谁的 setParent()。
尝试构造如下父子关系图:
def parentandChildren(self): obj0 = QObject() obj1 = QObject() obj2 = QObject() obj3 = QObject() obj4 = QObject() obj5 = QObject() obj1.setParent(obj0) obj2.setParent(obj0) obj3.setParent(obj1) obj4.setParent(obj2) obj5.setParent(obj2) print("obj0:", obj0) print("obj1:", obj1) print("obj2:", obj2) print("obj3:", obj3) print("obj4:", obj4) print("obj5:", obj5)
parent():获取父对象。
print("obj2's parent:", obj2.parent())
输出结果:obj2’s parent: <PyQt5.QtCore.QObject object at 0x0000022067D5D5E8>
可以看到 obj2 的父对象就是 obj0。
children():获取所有直接子对象。
print("obj0's children:", obj0.children())
输出结果:obj0’s children: [<PyQt5.QtCore.QObject object at 0x0000024A8172D678>, <PyQt5.QtCore.QObject object at 0x0000024A8172D708>]
可以看到 obj0 的直接子对象就是 obj1、obj2,这也证明 children() 只能找到直接子对象。
findChild(参数1, 参数2, 参数3):获取某一个指定名称和类型的子对象。
参数1:类型如 QObject;类型元组如 (QPushButton, QLabel)
参数2:名称 notice(可以省略)
参数3:查找选项
Qt.FindChildrenRecursively 递归查找,默认选项;
Qt.FindDirectChildrenOnly 只查找直接子对象;
测试过程:
print(obj0.findChild(QObject))
输出结果:<PyQt5.QtCore.QObject object at 0x000001B3C880D678> 即 obj1。
如果给 obj0 的子对象添加一个 label
label = QLabel()
label.setParent(obj0)
print(obj0.findChild(QLabel))
运行报错:
控件的父类项必须是一个控件(widget)
但 findChild 只能查到第一个子对象,怎么查到 obj2 呢?可以给 obj2 对象起个名字:
obj2.setObjectName("2")
print(obj0.findChild(QObject, "2"))
输出结果:<PyQt5.QtCore.QObject object at 0x000001C5CC34E708> 即 obj2。
如果查找一个不是子对象的名字会怎么样?
obj3.setObjectName("3")
print(obj0.findChild(QObject, "3"))
输出结果:<PyQt5.QtCore.QObject object at 0x000002296729E798> 能查到 obj3。
这是为什么?因为选项 3 是默认递归查找。再试一下改成只查找直接子对象的方式:
print(obj0.findChild(QObject, "3", Qt.FindDirectChildrenOnly))
输出结果:None。
以上测试演示了三个参数的作用:第一个是通过类型或类型元组来限定我们查找的子对象类型,第二个是通过名称做限定,第三个是设置查找选项是否是递归查找。
findChildren(参数1, 参数2, 参数3):获取某多个指定名称和类型的子对象。
参数同上。
print(obj0.findChildren(QObject))
输出结果:[<PyQt5.QtCore.QObject object at 0x0000026C59DCE678>, <PyQt5.QtCore.QObject object at 0x0000026C59DCE798>, <PyQt5.QtCore.QObject object at 0x0000026C59DCE708>, <PyQt5.QtCore.QObject object at 0x0000026C59DCE828>, <PyQt5.QtCore.QObject object at 0x0000026C59DCE8B8>]
输出了五项,证明查找了多个子对象,且默认的查找方式是递归查找。
对 Qt 对象内存管理机制的影响
QObject 继承树:
所有的对象都是直接或者间接继承自 QObject,也自然继承了 setParent 的方法,便于设置父子对象;
QObjects 在一个对象树中组织他们自己,当创建一个 QObject 时,如果使用了其他对象作为其父对象。那么,它就会被添加到父对象的 children() 列表中。当父对象被销毁时,这个 QObject 也会被销毁。(根节点删除,子节点自然会被删除)
测试:如果干掉父对象,子对象会不会自动被干掉。
def memoryManagement(self):
obj1 = QObject()
obj2 = QObject()
# obj1是父,obj2是子
obj2.setParent(obj1)
# 监听obj2对象被释放
obj2.destroyed.connect(lambda: print("obj2对象被释放了!"))
PS:lambda 定义了一个匿名函数,不会带来程序运行效率的提高,只会使代码更简洁。如果使用lambda,lambda 内不要包含循环,如果有,宁愿定义函数来完成,使代码获得可重用性和更好的可读性。总结:lambda 是为了减少单行函数的定义而存在的。
输出:obj2对象被释放了!
测试证明:obj1 会被自动释放,obj2 是绑定在 obj1 身上的,所以 obj1 被干掉,obj2 也会自动被干掉。
引用计数机制:PyObject 是每个对象必有的内容,当一个对象有新的引用时,它的 ob_refcnt 就会增加,当引用它的对象被删除,它的 ob_refcnt 就会减少,当引用计数为 0 时,该对象生命就结束了。
继续测试:self.obj1 = obj1 相当于把 obj1 作为了 self 对象的一个属性,有指针指向 obj1 这样一个对象,不会被自动释放。
def memoryManagement(self):
obj1 = QObject()
# self.obj1 = obj1
obj2 = QObject()
obj2.setParent(obj1)
# 监听obj2对象被释放
obj2.destroyed.connect(lambda: print("obj2对象被释放了!"))
输出:空白
(一开始每次把 Qt 窗口关了就输出被释放,百思不得其解,后来觉得可能是窗口关了以后会释放资源,结束程序,所以才释放。仔细观察,第一次测试时几乎是在窗口出现的同时就输出被释放的信息,但第二次就是不关窗口就不会显示,一关就打印 obj2 被释放,再一次佐证了我的想法。)
测试证明:当 obj1 对象作为 self 的属性,obj1 和 obj2 都没有被释放。
继续测试:如果手动把父对象 obj1 释放掉,子对象 obj2 也会被自动释放。
def memoryManagement(self):
obj1 = QObject()
self.obj1 = obj1
obj2 = QObject()
obj2.setParent(obj1)
# 监听obj2对象被释放
obj2.destroyed.connect(lambda: print("obj2对象被释放了!"))
del self.obj1
输出:obj2对象被释放了!
以上三组实验证明父子关系对内存管理机制是有影响的,一旦父对象被干掉,子对象会被自动干掉。如果用在控件中会有什么影响?
QWidget 扩展了父-子关系:当一个控件设置了父控件,会包含在父控件内部(体现在 GUI 控件展示层面上),受父控件区域裁剪(子控件不可能超出父控件的范围),父控件被删除时,子控件会自动删除(窗口关闭后上面的图标也会被关闭,节省内存)。
场景案例:一个对话框,上面有很多操作按钮(取消, OK),按钮和对话框本身是父子控件关系,操作的时候,是操作的对话框控件本身,而不是其内部的子控件(按钮),当对话框被删除时,内部的子控件也会自动的删除,这就非常合理。
对 Qt 控件的影响
如果一个控件,没有任何父控件,那么就会被当成顶层控件(窗口),多个顶层窗口相互独立。如果想要一个控件被包含在另外一个控件内部,就需要设置父子关系。显示位置受父控件约束,生命周期也被父对象接管。
创建两个独立的窗口。
要求:设置不同的标题。
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win1 = QWidget()
win1.setWindowTitle("学习是多么一件美逝啊")
win1.setStyleSheet("background-color:purple")
win1.show()
win2 = QWidget()
win2.setWindowTitle("啊,像中枪一样")
win2.show()
# window = Window()
# window.show()
sys.exit(app.exec_())
创建一个窗口,包含另外两个子控件。
要求:两个子控件必须在同一个窗口内部。
if __name__ == '__main__': import sys app = QApplication(sys.argv) win_root = QWidget() win_root.setWindowTitle("这是那个根") # win_root.setStyleSheet("background-color:purple") win1 = QLabel(win_root) win1.setText("学习是多么一件美逝啊") # win1.show() win2 = QPushButton() win2.setParent(win_root) win2.move(100, 100) win2.setText("啊,像中枪一样") # win2.show() win_root.show() sys.exit(app.exec_())
PS:父控件的设置有两种方法,win1 = QLabel(win_root)
放在定义时的括号里,本质上是在用QLabel对象的init方法里的setparent()函数,和win2.setParent(win_root)
是一样的。
错过:
move()是移动,resize()是调整大小。
把win_root.show()放到win1和win2之前,导致界面显示出来时还没有子控件的定义,也自然显示不出来了。
创建一个窗口,包含多个子控件。
要求:让所有的QLabel类型子控件都设置背景颜色为cyan,即使后续再增加新的子控件。
可以遍历子控件并加以过滤,children不能过滤,findChildren可过滤。
for sub_widget in win_root.findChildren(QLabel):
sub_widget.setStyleSheet("background-color:cyan;")
(例子见(二)信号部分)
widget.信号.connect(槽):连接信号与槽。
obj.disconnect():取消连接信号与槽。
obj可以是控件——取消控件的所有信号连接;也可以是信号——取消指定信号的连接。
widget.blockSignals(bool):临时(取消)阻止指定控件所有的信号与槽的连接,根据bool来决定。
widget.signalsBlocked():获取信号是否被阻止。
widget.receivers(信号):返回连接到信号的接收器数量。
应用场景:监听信号, 响应用户行为。
基本概念:信号(Signal)和槽(Slot)是Qt中的核心机制, 主要作用在于对象之间进行通讯。所有继承自QWidget的控件都支持"信号与槽"的机制。
信号与槽如下图:
Qt中的信号与槽:
信号:当一个控件的状态发生改变时, 向外界发出的信息。
槽:一个执行某些操作的函数/方法。
机制描述:手动操作:连接信号与槽;自动操作:当信号发出时,连接的槽函数会自动执行。
基本使用介绍:
信号:控件内置的一些函数,比如按下按钮QPushButton().pressed、按下并弹起按钮QPushButton().clicked…;也可以自定义pyqtSignal()。
槽:同信号,有控件内置的槽函数;也有自定义的函数/方法。
连接方式:object.信号.connect(槽函数)。
特性:
一个信号可以连接多个槽函数;
一个信号也可以连接另外一个信号(联动);
信号的参数可以是任何Python类型;(自定义传参)
一个槽可以监听多个信号(信号之间是或的关系,有一个发生就行)…
当用户点击按钮的时候, 打印"有事吗?"
要求:按钮创建, 设置;监听按钮点击的信号(click)。
# 信号与槽案例1
def example1(self):
# 按钮要加在窗口身上
but = QPushButton(self)
but.setText("点点")
def print_doingwhat():
print("有事吗?")
but.clicked.connect(print_doingwhat)
在所有修改的窗口标题前,添加前缀"加油-"
要求:设置窗口标题;监听窗口标题改变信号;临时取消/恢复信号与槽的连接。
错误解法:
# 信号与槽案例2
def add_comeon(title):
# 用 + 来拼接
window.setWindowTitle("加油-" + title)
window.windowTitleChanged.connect(add_comeon)
window.setWindowTitle("Hello1")
window.setWindowTitle("Hello2")
window.setWindowTitle("Hello3")
报错:Process finished with exit code -1073740791 (0xC0000409)
原因:设置窗口标题会调用槽函数,但槽函数里面又会调用该方法,死循环了。应该改成槽函数里暂停监控。修改如下:
window.blockSignals(True)
window.setWindowTitle("加油-" + title)
window.blockSignals(False)
注意:connect,disconnect前面是信号,如window.windowTitleChanged;而blockSignals前面是对象,如window。
isWidgetType():是否是控件类型,继承自QWidget类。
# 类型判定
def Type_judgement(self):
obj = QObject()
w = QWidget()
btn = QPushButton()
label = QLabel()
objs = [obj, w, btn, label]
for o in objs:
print("是否是widget类型?")
print(o.isWidgetType())
inherits(父类):一个对象是否继承(直接或者间接)自某个类。
objs = [obj, w, btn, label]
for o in objs:
print(o.inherits("QPushButton"))
过滤筛选控件
创建一个窗口,包含多个QLabel或其他控件。将包含在窗口内所有的QLabel控件,设置背景色cyan。
涉及知识点:子控件获取;控件类型判定;样式设置。
方法一:
# 控件过滤案例
def example2(self):
# self就是控件
label = QLabel(self)
label.setText("标签在此")
label2 = QLabel(self)
label2.setText("标签2在此")
label2.move(100, 100)
btn = QPushButton(self)
btn.setText("按钮在此")
btn.move(200, 200)
for widget in self.children():
if widget.inherits("QLabel"):
widget.setStyleSheet("background-color:cyan")
方法二:
for widget in self.findChildren(QLabel):
widget.setStyleSheet("background-color:cyan")
PS:children()获取所有子控件,findchildren()可获得指定类型的子控件。
obj.deleteLater():删除一个对象时,也会解除它与父对象之间的关系。
deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。这样做的好处是可以在这些延迟删除的时间内完成一些操作(比如案例的print),坏处就是内存释放会不及时。
例如:若有如下父子关系,箭头代表父引用子,定义了window(self)里一个属性指向obj1。
# 删除对象 def deleteObject(self): obj1 = QObject() obj2 = QObject() obj3 = QObject() self.obj1 = obj1 obj3.setParent(obj2) obj2.setParent(obj1) 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())
PS:del obj2 删除了栈中临时变量对堆中真正对象的指向,不会释放obj2(还有obj1的引用)
运行结果:obj2、obj3会被释放,同时解除父子关系,但释放会在下一次循环才会实现,本次还会走完主消息循环。
想要移除某一个对象的时候使用。
监听、过滤一些底层事件,后续细讲,此处暂作了解。
拦截事件, 监听特定行为;
事件机制:信号与槽机制(更上层,高级封装)是对事件机制(更底层)的高级封装,信号与槽机制用于通讯。
一个应用程序会有两个队列来存放不同类别的事件消息,一旦应用程序调用exec方法,会开启一个消息循环。
分发事件消息:
evt 的事件类型:
import sys from PyQt5.Qt import * app = QApplication(sys.argv) window = QWidget() btn = QPushButton(window) btn.setText("按钮") btn.move(100, 100) btn.pressed.connect(lambda: print("按下按钮")) window.show() sys.exit(app.exec_())
解释整个过程:在操作系统中有python(上述程序)这样一个应用程序在跑,当用户按下按钮时,会产生一个事件消息,操作系统首先接收到这个事件消息,发现该消息是产生在python应用程序里的,所以操作系统会把该事件消息分发给该应用程序的消息队列。应用程序的消息循环开启后(app.exec())会不断循环消息队列,不断扫描队列是否有新消息。扫到之后把新事件消息包装成QEvent对象进行分发处理,分发给QApplication对象里的notify方法。接着根据receiver把事件传递给事件接收者(比如按钮)的event方法,再根据事件类型调用具体的事件函数,事件函数内部又会对外部发射一个信号,该信号所连接的槽就会执行。
证明:继承QApplication类后重写notify方法。
class App(QApplication):
def notify(self, receiver, evt):
if receiver.inherits("QPushButton") and evt.type() == QEvent.MouseButtonPress:
print(receiver, evt)
return super(App, self).notify(receiver, evt)
app = App(sys.argv)
如果判定成功不分发:
if receiver.inherits("QPushButton") and evt.type() == QEvent.MouseButtonPress:
print(receiver, evt)
else:
return super(App, self).notify(receiver, evt)
不仅槽函数不会执行,程序还崩了。
改好后,继续传,会传给按钮的event方法,接下来拦截按钮里的event方法:
class Btn(QPushButton):
def event(self, evt):
print(evt)
return super(Btn, self).event(evt)
可以看到有很多事件类型,需要过滤出鼠标点击事件:
过滤后:
class Btn(QPushButton):
def event(self, evt):
if evt.type() == QEvent.MouseButtonPress:
print(evt)
return super(Btn, self).event(evt)
def mousePressEvent(self, evt):
print("鼠标被按下了")
return super().mousePressEvent(evt)
如果没有return super().mousePressEvent(evt)是不会输出“放下按钮”的,发出btn.pressed信号在父类里面。
startTimer(ms, Qt.TimerType) -> timer_id:开启一个定时器。
timer_id:返回值,定时器的唯一标识。
ms:整型,几个毫秒记一次数。
Qt.TimerType:
Qt.PreciseTimer(精确定时器:毫秒准确);
Qt.CoarseTimer(粗定时器:5%的误差间隔);
Qt.VeryCoarseTimer(很粗的定时器:秒级)。
原理上来说越精确越好,但越精确越耗费性能。
killTimer(timer_id):根据定时器ID,杀死定时器。
timerEvent():定时器执行事件,每隔ms个毫秒执行一次timerEvent()方法,可继承后重写。
轮询、倒计时…
创建一个窗口,并设置一个子控件QLabel。要求:展示10s倒计时,每隔一秒减一,倒计时结束就停止计时。
涉及知识点:标签的创建和设置;自定义标签类的封装;定时器的使用。
# 0.导入包和模块 from PyQt5.Qt import * import sys class NewLabel(QLabel): def timerEvent(self, evt): # 获取当前标签的内容 current_sec = int(self.text()) current_sec -= 1 self.setText(str(current_sec)) # 1.创建应用程序对象 app = QApplication(sys.argv) # 2.1 创建控件 window = QWidget() # 2.2 设置控件 window.setWindowTitle("定时器的使用") window.resize(500, 500) label = NewLabel(window) label.setText("10") label.move(100, 100) # qss样式表 label.setStyleSheet("font-size: 40px;") label.startTimer(1000) # 2.3 展示控件 window.show() # 3.进入消息循环 sys.exit(app.exec_())
PS:QLabel的父类是QObject,因此也有startTimer()方法可以用。倒计时操作通过修改timerEvent来实现。
上述代码还有一个问题是倒计时到0后不会停止,而是会变成负数。想到的方法是判断倒计时到0后杀掉计时器,这就需要计数器的id,但id在类外面,当前数字在类里面,要怎么传进去呢。进一步想到把label有关的都封装到类的init方法里面。
# 0.导入包和模块 from PyQt5.Qt import * import sys class NewLabel(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setText("10") self.move(100, 100) # qss样式表 self.setStyleSheet("font-size: 40px;") # 通过把time_id绑在self上,使其由一个局部变量变成一个属性。 self.time_id = self.startTimer(1000) def timerEvent(self, evt): # 获取当前标签的内容 current_sec = int(self.text()) current_sec -= 1 self.setText(str(current_sec)) if current_sec == 0: print("停止") self.killTimer(self.time_id) # 1.创建应用程序对象 app = QApplication(sys.argv) # 2.1 创建控件 window = QWidget() # 2.2 设置控件 window.setWindowTitle("定时器的使用") window.resize(500, 500) label = NewLabel(window) # 2.3 展示控件 window.show() # 3.进入消息循环 sys.exit(app.exec_())
高度的封装虽然会带来方便,但缺乏灵活性。
扩展:如果倒计时的数字和开始时机也由自己设置。
# 0.导入包和模块 from PyQt5.Qt import * import sys class NewLabel(QLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # self.setText("10") self.move(100, 100) # qss样式表 self.setStyleSheet("font-size: 40px;") # 通过把time_id绑在self上,使其由一个局部变量变成一个属性。 # self.time_id = self.startTimer(1000) def setSec(self, sec): self.setText(str(sec)) def start(self, ms): self.time_id = self.startTimer(ms) def timerEvent(self, evt): # 获取当前标签的内容 current_sec = int(self.text()) current_sec -= 1 self.setText(str(current_sec)) if current_sec == 0: print("停止") self.killTimer(self.time_id) # 1.创建应用程序对象 app = QApplication(sys.argv) # 2.1 创建控件 window = QWidget() # 2.2 设置控件 window.setWindowTitle("定时器的使用") window.resize(500, 500) label = NewLabel(window) label.setSec(5) label.start(2000) # 2.3 展示控件 window.show() # 3.进入消息循环 sys.exit(app.exec_())
创建一个窗口,通过定时器不断增加该窗口的尺寸大小。要求:每100ms宽高均增加1px(1个像素点)。
涉及知识点:窗口控件的封装;定时器的使用。
from PyQt5.Qt import * import sys class NewWidget(QWidget): def __init__(self): super().__init__() self.setWindowTitle("定时器的使用") self.resize(500, 500) self.startTimer(100) def timerEvent(self, evt): current_w = self.width() current_h = self.height() self.resize(current_w + 1, current_h + 1) app = QApplication(sys.argv) window = NewWidget() window.show() sys.exit(app.exec_())
多语言国际化支持
例1:
# 信号操作
def Signal_operation(self):
self.obj = QObject()
def destoryed_privateSlots():
print("对象被释放了")
self.obj.destroyed.connect(destoryed_privateSlots)
del self.obj
运行结果:
PS:
其中主函数中window创建时自动调用init函数,init函数里面又调用了setup_ui函数,setup_ui函数调用Signal_operation函数。
创建对象时,
若使用 obj = QObject()
obj是一个局部变量,调用完Signal_operation函数自然会被释放,因为obj没有指针指向它,引用计数器到后面是0。
如果改成 self.obj = QObject()
相当于把obj绑在了self身上,self也就是window,窗口对象不会被释放,obj也不会被自动释放。
用destoryed传递参数,在destoryed上【CTRL+鼠标左键】查看具体函数。
def destroyed(self, p_object=None): # real signature unknown; restored from __doc__
""" destroyed(self, object: QObject = None) [signal] """
pass
其中p_object可以向外界传递是哪个对象被释放了,可以用槽函数接收一下。
修改如下:
def destoryed_privateSlots(obj):
print("对象被释放了", obj)
现在的运行结果:
例二:
# 信号操作
def Signal_operation(self):
self.obj = QObject()
def objName_privateSlots(obj):
print("对象名称发生了改变:", obj)
self.obj.objectNameChanged.connect(objName_privateSlots)
self.obj.setObjectName("新名字鸭")
运行结果:
例三:disconnect
# 信号操作
def Signal_operation(self):
self.obj = QObject()
def objName_privateSlots(obj):
print("对象名称发生了改变:", obj)
self.obj.objectNameChanged.connect(objName_privateSlots)
self.obj.setObjectName("新名字1")
self.obj.objectNameChanged.disconnect()
self.obj.setObjectName("新名字2")
运行结果:改新名字2时信号仍然会释放,但与槽函数的连接已经断了。
例四:blockSignals
def Signal_operation(self):
self.obj = QObject()
def objName_privateSlots(obj):
print("对象名称发生了改变:", obj)
self.obj.objectNameChanged.connect(objName_privateSlots)
self.obj.setObjectName("新名字1")
self.obj.blockSignals(True) # 临时阻断连接
self.obj.setObjectName("新名字2")
self.obj.blockSignals(False) # 恢复连接
self.obj.setObjectName("新名字3")
self.obj.blockSignals(True)
self.obj.setObjectName("新名字4")
运行结果:只有1、3的改变被打印出来了。
例五:signalBlocked
# 信号操作 def Signal_operation(self): self.obj = QObject() def objName_privateSlots(obj): print("对象名称发生了改变:", obj) self.obj.objectNameChanged.connect(objName_privateSlots) print(self.obj.signalsBlocked(), "1") self.obj.setObjectName("新名字1") self.obj.blockSignals(True) print(self.obj.signalsBlocked(), "2") self.obj.setObjectName("新名字2") self.obj.blockSignals(False) print(self.obj.signalsBlocked(), "3") self.obj.setObjectName("新名字3") self.obj.blockSignals(True) print(self.obj.signalsBlocked(), "4") self.obj.setObjectName("新名字4")
运行结果:True表示临时断开连接,False表示恢复连接。
例六:receivers,括号里放信号
# 信号操作 def Signal_operation(self): self.obj = QObject() def objName_privateSlots(obj): print("对象名称发生了改变:", obj) self.obj.objectNameChanged.connect(objName_privateSlots) def objName_privateSlots2(obj): print("对象名称发生了改变:", obj) self.obj.objectNameChanged.connect(objName_privateSlots2) def objName_privateSlots3(obj): print("对象名称发生了改变:", obj) self.obj.objectNameChanged.connect(objName_privateSlots3) print(self.obj.receivers(self.obj.objectNameChanged)) self.obj.setObjectName("新名字1")
运行结果:不仅显示了连接三个槽,还证明了信号与槽的连接关系是多对多,一个信号可连接多个槽。
笔记整理自网课。
用到的博客:
简单方法计算 mro:https://www.cnblogs.com/springionic/p/12218429.html
qss 样式表:https://www.cnblogs.com/yinsedeyinse/p/11701466.html
python lambda 介绍:https://www.cnblogs.com/evening/archive/2012/03/29/2423554.html
python 引用计数器机制:https://www.cnblogs.com/TM0831/p/10599716.html
pycharm调试bug Process finished with exit code -1073740791 (0xC0000409):https://www.cnblogs.com/ranzhong/p/13875853.html
小技巧:【ctrl + D】Pycharm 快速复制当前行到下一行。
作话:9月份的笔记居然拖到现在才做完,太可怕了,这样下去是学不完的哇,学不完毕设是自己做不出来的哇,如何面对江东父老。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。