6 - PyQt5 基类 QObject

6 - PyQt5 基类 QObject

image-20210912165146034

从上图列出的所有基类可以看到,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 的基类) 。

image-20210912170743130

(一)功能作用

1、对象名称,属性
API
  • 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())
    

运行结果:

image-20210912195107664

应用场景
  • 用于 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")

image-20210912200519429

其中 “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)

运行效果:

image-20210912205557141

从运行效果可以看到,实现了从 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;
}

测试结果:

image-20210912211809849

可以看到只有对象名称为 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是属性的值。

实现效果:

image-20210912213011962

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

2、父子对象的操作
API(Application Programming Interface,应用程序接口)
  • setParent(parent):设置父对象,父对象只能设置一个,设置多个会被新设置的给覆盖掉。给谁设置父对象,就调用谁的 setParent()。

    尝试构造如下父子关系图:

image-20210914142713630

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)

image-20210914142802400

  • 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))
    

    运行报错:

    image-20210914154757027

    控件的父类项必须是一个控件(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 = 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),按钮和对话框本身是父子控件关系,操作的时候,是操作的对话框控件本身,而不是其内部的子控件(按钮),当对话框被删除时,内部的子控件也会自动的删除,这就非常合理。

  2. 对 Qt 控件的影响

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

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

    要求:设置不同的标题。

    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_())
    

    image-20220122170602081

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

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

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

    image-20220122171739913

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

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

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

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

    image-20220122211250520

3、信号处理
API

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

  • widget.信号.connect(槽):连接信号与槽。

  • obj.disconnect():取消连接信号与槽。

    obj可以是控件——取消控件的所有信号连接;也可以是信号——取消指定信号的连接。

  • widget.blockSignals(bool):临时(取消)阻止指定控件所有的信号与槽的连接,根据bool来决定。

  • widget.signalsBlocked():获取信号是否被阻止。

  • widget.receivers(信号):返回连接到信号的接收器数量。

    应用场景:监听信号, 响应用户行为。

信号与槽机制

基本概念:信号(Signal)和槽(Slot)是Qt中的核心机制, 主要作用在于对象之间进行通讯。所有继承自QWidget的控件都支持"信号与槽"的机制。

信号与槽如下图:

image-20220122214340256

Qt中的信号与槽:

image-20220122214638919

信号:当一个控件的状态发生改变时, 向外界发出的信息。

槽:一个执行某些操作的函数/方法。

机制描述:手动操作:连接信号与槽;自动操作:当信号发出时,连接的槽函数会自动执行。

基本使用介绍

信号:控件内置的一些函数,比如按下按钮QPushButton().pressed、按下并弹起按钮QPushButton().clicked…;也可以自定义pyqtSignal()。

槽:同信号,有控件内置的槽函数;也有自定义的函数/方法。

连接方式:object.信号.connect(槽函数)。

特性:

一个信号可以连接多个槽函数;

image-20220122215615216

一个信号也可以连接另外一个信号(联动);

image-20220122215624461

信号的参数可以是任何Python类型;(自定义传参)

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

image-20220122215739461

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

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

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

    image-20220128215321377

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

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

    错误解法:

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

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())
    

    image-20220128222544202

  • inherits(父类):一个对象是否继承(直接或者间接)自某个类。

    image-20220128222748156

            objs = [obj, w, btn, label]
            for o in objs:
                print(o.inherits("QPushButton"))
    

    image-20220128222918466

应用场景

过滤筛选控件

案例-控件过滤

创建一个窗口,包含多个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。

image-20220129113950184

    # 删除对象
    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会被释放,同时解除父子关系,但释放会在下一次循环才会实现,本次还会走完主消息循环。

image-20220129190813458

应用场景

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

6、事件处理
API
  • childEvent()
  • customEvent()
  • eventFilter()
  • installEventFilter()
  • removeEventFilter
  • event():细化分发。

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

应用场景

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

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

image-20220129191946471

一个应用程序会有两个队列来存放不同类别的事件消息,一旦应用程序调用exec方法,会开启一个消息循环。

image-20220129192014154

分发事件消息:

image-20220130213819653

image-20220130214450870

image-20220129192733019

evt 的事件类型:

image-20220130211303253

案例
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_())

image-20220130114846996

解释整个过程:在操作系统中有python(上述程序)这样一个应用程序在跑,当用户按下按钮时,会产生一个事件消息,操作系统首先接收到这个事件消息,发现该消息是产生在python应用程序里的,所以操作系统会把该事件消息分发给该应用程序的消息队列。应用程序的消息循环开启后(app.exec())会不断循环消息队列,不断扫描队列是否有新消息。扫到之后把新事件消息包装成QEvent对象进行分发处理,分发给QApplication对象里的notify方法。接着根据receiver把事件传递给事件接收者(比如按钮)的event方法,再根据事件类型调用具体的事件函数,事件函数内部又会对外部发射一个信号,该信号所连接的槽就会执行。

证明:继承QApplication类后重写notify方法。

image-20220130115821467

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)

image-20220130122828828

如果判定成功不分发:

        if receiver.inherits("QPushButton") and evt.type() == QEvent.MouseButtonPress:
            print(receiver, evt)
        else:
            return super(App, self).notify(receiver, evt)

不仅槽函数不会执行,程序还崩了。

image-20220130123046140

改好后,继续传,会传给按钮的event方法,接下来拦截按钮里的event方法:

class Btn(QPushButton):
    def event(self, evt):
        print(evt)
        return super(Btn, self).event(evt)

可以看到有很多事件类型,需要过滤出鼠标点击事件:

image-20220130211203681

过滤后:

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)

image-20220130213158473

如果没有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 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_())
    
  2. 创建一个窗口,通过定时器不断增加该窗口的尺寸大小。要求:每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_())
    
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

运行结果:

image-20220128122750088

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)
    

    现在的运行结果:

    image-20220128123642211

例二:

    # 信号操作
    def Signal_operation(self):
        self.obj = QObject()

        def objName_privateSlots(obj):
            print("对象名称发生了改变:", obj)
        self.obj.objectNameChanged.connect(objName_privateSlots)

        self.obj.setObjectName("新名字鸭")

运行结果:

image-20220128124134934

例三: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时信号仍然会释放,但与槽函数的连接已经断了。

image-20220128124803600

例四: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的改变被打印出来了。

image-20220128125737123

例五: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表示恢复连接。

image-20220128130335443

例六: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")

运行结果:不仅显示了连接三个槽,还证明了信号与槽的连接关系是多对多,一个信号可连接多个槽。

image-20220128131025123


笔记整理自网课。

用到的博客:

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值