6 - PyQt5 基类 QObject

从上图列出的所有基类可以看到,QObject 是所有的 Qt 对象的基类。

那么,QObejct 的父类是什么呢?这就需要用到 mro。

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 的基类) 。

(一)功能作用

1、对象名称,属性

API
  • setObjectName(“唯一名称”):给一个 Qt 对象设置一个名称,一般这个名称是唯一的,当做对象的 ID 来使用。

  • objectName():获取一个 Qt 对象的名称。

  • setProperty(“属性名称”,值):给一个 Qt 对象动态的添加一个属性与值。

  • property(“属性名称”):获取一个对象的属性

  • dynamicPropertyNames():获取一个对象中所有通过 setProperty() 设置的属性名称

    # 存放所有子控件以及子控件的配置操作
    def setup_ui(self):# self.test_inheritance()self.nameandAttribute()def nameandAttribute(self):# 测试APIobj = QObject()obj.setObjectName("notice")print(obj.objectName())obj.setProperty("level1", "error")obj.setProperty("level2", "warning")print(obj.property("level1"))print(obj.dynamicPropertyNames())
    

运行结果:

应用场景
  • 用于 qss 的 ID 选择器,属性选择器 ——> 方便统一设置样式。
  • 用于装饰器的信号与槽
案例:创建多个用于信息提示的 QLabel

要求:

  1. 凡是提示的 QLabel 控件,都要求设置:

    字体大小为 20px;

    字体颜色为灰色;

    边框圆角为 8px。

  2. 信息提示分多个级别:

    正常 (normal)——绿色边框、绿色字体。

    警告 (warning)——黄色边框、黄色字体。

    错误 (error)——红色边框、红色字体。

涉及知识点:

  1. qss 样式表
  2. 文件读取。
  3. 对象 \ 属性名称设置。

先介绍一下 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是属性的值。

实现效果:

到这儿就基本实现了案例的要求。

2、父子对象的操作

API(Application Programming Interface,应用程序接口)
  • 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>]

    输出了五项,证明查找了多个子对象,且默认的查找方式是递归查找。

应用场景
  1. 对 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 = obj1obj2 = 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 = obj1obj2 = QObject()obj2.setParent(obj1)# 监听obj2对象被释放obj2.destroyed.connect(lambda: print("obj2对象被释放了!"))del self.obj1
    

    输出:obj2对象被释放了!

    以上三组实验证明父子关系对内存管理机制是有影响的,一旦父对象被干掉,子对象会被自动干掉。如果用在控件中会有什么影响?

    QWidget 扩展了父-子关系:当一个控件设置了父控件,会包含在父控件内部(体现在 GUI 控件展示层面上),受父控件区域裁剪(子控件不可能超出父控件的范围),父控件被删除时,子控件会自动删除(窗口关闭后上面的图标也会被关闭,节省内存)。

    场景案例:一个对话框,上面有很多操作按钮(取消, OK),按钮和对话框本身是父子控件关系,操作的时候,是操作的对话框控件本身,而不是其内部的子控件(按钮),当对话框被删除时,内部的子控件也会自动的删除,这就非常合理。

  2. 对 Qt 控件的影响

    如果一个控件,没有任何父控件,那么就会被当成顶层控件(窗口),多个顶层窗口相互独立。如果想要一个控件被包含在另外一个控件内部,就需要设置父子关系。显示位置受父控件约束,生命周期也被父对象接管。

案例
  1. 创建两个独立的窗口。

    要求:设置不同的标题。

    if __name__ == '__main__':import sysapp = 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_())
    

  2. 创建一个窗口,包含另外两个子控件。

    要求:两个子控件必须在同一个窗口内部。

    if __name__ == '__main__':import sysapp = 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之前,导致界面显示出来时还没有子控件的定义,也自然显示不出来了。

  3. 创建一个窗口,包含多个子控件。

    要求:让所有的QLabel类型子控件都设置背景颜色为cyan,即使后续再增加新的子控件。

    可以遍历子控件并加以过滤,children不能过滤,findChildren可过滤。

        for sub_widget in win_root.findChildren(QLabel):sub_widget.setStyleSheet("background-color:cyan;")
    

3、信号处理

API

(例子见(二)信号部分)

  • 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类型;(自定义传参)

一个槽可以监听多个信号(信号之间是或的关系,有一个发生就行)…

案例
  1. 当用户点击按钮的时候, 打印"有事吗?"

    要求:按钮创建, 设置;监听按钮点击的信号(click)。

        # 信号与槽案例1def example1(self):# 按钮要加在窗口身上but = QPushButton(self)but.setText("点点")def print_doingwhat():print("有事吗?")but.clicked.connect(print_doingwhat)
    

  2. 在所有修改窗口标题前,添加前缀"加油-"

    要求:设置窗口标题;监听窗口标题改变信号;临时取消/恢复信号与槽的连接。

    错误解法:

        # 信号与槽案例2def 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。

4、类型判定

API
  • 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()可获得指定类型的子控件。

5、对象删除

API

obj.deleteLater():删除一个对象时,也会解除它与父对象之间的关系。

deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。这样做的好处是可以在这些延迟删除的时间内完成一些操作(比如案例的print),坏处就是内存释放会不及时。

例如:若有如下父子关系,箭头代表父引用子,定义了window(self)里一个属性指向obj1。

    # 删除对象def deleteObject(self):obj1 = QObject()obj2 = QObject()obj3 = QObject()self.obj1 = obj1obj3.setParent(obj2)obj2.setParent(obj1)obj1.destroyed.connect(lambda: print("obj1被释放!"))obj2.destroyed.connect(lambda: print("obj2被释放!"))obj3.destroyed.connect(lambda: print("obj3被释放!"))# del obj2obj2.deleteLater()print(obj1.children())

PS:del obj2 删除了栈中临时变量对堆中真正对象的指向,不会释放obj2(还有obj1的引用)

运行结果:obj2、obj3会被释放,同时解除父子关系,但释放会在下一次循环才会实现,本次还会走完主消息循环。

应用场景

想要移除某一个对象的时候使用。

6、事件处理

API
  • childEvent()
  • customEvent()
  • eventFilter()
  • installEventFilter()
  • removeEventFilter
  • event():细化分发。

监听、过滤一些底层事件,后续细讲,此处暂作了解。

应用场景

拦截事件, 监听特定行为;

事件机制:信号与槽机制(更上层,高级封装)是对事件机制(更底层)的高级封装,信号与槽机制用于通讯。

一个应用程序会有两个队列来存放不同类别的事件消息,一旦应用程序调用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信号在父类里面。

7、定时器

API
  • 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()方法,可继承后重写。

应用场景

轮询、倒计时…

案例
  1. 创建一个窗口,并设置一个子控件QLabel。要求:展示10s倒计时,每隔一秒减一,倒计时结束就停止计时。

    涉及知识点:标签的创建和设置;自定义标签类的封装;定时器的使用。

    # 0.导入包和模块
    from PyQt5.Qt import *
    import sysclass NewLabel(QLabel):def timerEvent(self, evt):# 获取当前标签的内容current_sec = int(self.text())current_sec -= 1self.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 sysclass 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 -= 1self.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 sysclass 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 -= 1self.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_())
    
  2. 创建一个窗口,通过定时器不断增加该窗口的尺寸大小。要求:每100ms宽高均增加1px(1个像素点)。

    涉及知识点:窗口控件的封装;定时器的使用。

    from PyQt5.Qt import *
    import sysclass 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_())
    

8、语言翻译

API
  • tr()
应用场景

多语言国际化支持

(二)信号

  • objectNameChanged (objectName):对象名称发生改变时,发射此信号。
  • destroyed (obj):对象被销毁时, 发射此信号。

例1:

# 信号操作
def Signal_operation(self):self.obj = QObject()def destoryed_privateSlots():print("对象被释放了")self.obj.destroyed.connect(destoryed_privateSlots)del self.obj

运行结果:

PS:

  1. 其中主函数中window创建时自动调用init函数,init函数里面又调用了setup_ui函数,setup_ui函数调用Signal_operation函数。

  2. 创建对象时,

    若使用 obj = QObject()
    

    obj是一个局部变量,调用完Signal_operation函数自然会被释放,因为obj没有指针指向它,引用计数器到后面是0。

    如果改成 self.obj = QObject()
    

    相当于把obj绑在了self身上,self也就是window,窗口对象不会被释放,obj也不会被自动释放。

  3. 用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月份的笔记居然拖到现在才做完,太可怕了,这样下去是学不完的哇,学不完毕设是自己做不出来的哇,如何面对江东父老。。。

6 - PyQt5 基类 QObject相关推荐

  1. 二,控件学习--2.控件基类-QObject

    学习计划 功能作用 1.对象名称,属性. 应用场景 进行控件样式的分组管理 from PyQt5.Qt import *class Window(QWidget):def __init__(self) ...

  2. 八、pyqt5按钮类控件——QPushButton、QRadioButton、QCheckBox

    pyqt5中常用的按钮类控件有QPushButton.QRadioButton.QCheckBox.QToolButton等.这些按钮类的基类都是QAbstracButton类.所以这些类有部分方法是 ...

  3. C++编程进阶6(public继承与组合、private继承、多重继承、处理模板基类内的名称、如何避免模板代码膨胀)

    二十一.public继承与组合 public继承是是子类对象is a基类对象的关系,比如QT中的所有组件类都要继承QObject,所以所有的QT组件都是一个QObject. 而组合是has a(包含) ...

  4. Qt (高仿Visio)流程图组件开发(三) 图元基类如何定义,流程图多种图元类型实现

    文章目录 本系列目录 前言 一.图元基类的定义 1.图元信息基类结构体 2.图元位置 3.父子对象关系 二.自定义图元实现 1.自定义图元基类(FlowchartGraphicsItem)与Qt原生图 ...

  5. 派生类参数初始化列表和基类构造函数顺序

    今天被问到了一个问题,随便回了一句,父类还没有构建,怎么能初始化父类的成员. 派生类构造函数的参数初始化列表,为什么不能初始化基类的成员? 例如下面的是不可以的 class Rectangle : p ...

  6. 基类的析构函数为什么要设置成virtual

    今天在一场面试过程中碰到这个问题,当时一时片刻没有反应过来,一头雾水.只记得自己回到说,是会造成内存泄露,但面试穷追猛打,一直追问为什么造成内存泄露,还给举例说明,一般情况下是不会造成内存泄露的,搞得 ...

  7. 经常用得到的安卓数据库基类

    //创建数据库 public class DBCreate { public static void CreateDatabase(SQLiteDatabase db) { db.beginTrans ...

  8. C++派生类与基类构造函数调用次序

    本文用来测试C++基类和派生类构造函数,析构函数,和拷贝构造函数的调用次序. 运行环境:SUSE Linux Enterprise Server 11 SP2  (x86_64) #include & ...

  9. qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7851 ...

最新文章

  1. php singleton()
  2. 天下武功,唯快不破,论推荐系统的“实时性”
  3. Ospaf项目-commits词频统计模块
  4. C语言再学习 -- 修改linux文件权限
  5. 【1024创造营】精彩课程回顾
  6. C#中改变进度条(progressbar)的颜色
  7. 2dpsk调制解调实验matlab_ila抓取数据,matlab分析,调试AD9361信号通路
  8. Silverlight使用RSA加密socket tcp通讯数据
  9. fiddler限速/弱网模拟
  10. Spark常用API(五)
  11. vs 执行单个文件,如cpp
  12. Cadence、Pspice 软件相关汇总
  13. win10开机桌面假死鼠标能动怎么办
  14. 亲密接触Redis-第一天
  15. 新旧版MATLAB中的希尔伯特-黄变换(HHT)及其边际谱的求取问题
  16. Unity 游戏入门 九、 精灵动画 Sprite Animation
  17. 成熟男人与24岁女孩精彩对白 —非常感动
  18. PCIE switch 连接绿联SSD
  19. C语言strchr()函数以及strstr()函数的实现
  20. 用VMware搭建云桌面

热门文章

  1. ARM9串行通信里面的 nRTS 和nCTS是什么意思?
  2. 性能测试------LoadRunner
  3. 机器人专用符文_LOL机器人符文怎么点
  4. 容器安装java_用容器重新定义 Java 虚拟化部署
  5. Windows系统安装免费的开源虚拟机软件VirtualBox
  6. 360手机新品牌5月6日公布 周鸿祎席地而坐谈AK47
  7. Zabbix Httpd漏洞修复
  8. Win11 通过无线网卡共享有线网络的方法(移动热点)
  9. 28.【C语言】键盘输入一句英文,统计句子中每个字母出现的次数(不区分大小写,一律按小写显示)(Demo)
  10. 如何生成一个安卓和苹果都能识别的二维码