当前位置: 首页 > news >正文

Python——PyQt5初体验

第一章 PyQt5初体验


目录

第一章 PyQt5初体验

前言

 一、软件准备

二、初体验

三、程序结构分析

3.1、PyQt5 应用程序基本结构

3.2、关键组件解析

3.3、事件循环机制

3.4、注意事项

四、QObject API

4.1、QObject 基础概念

4.1.1 对象树与内存管理

4.1.2 对象命名与查找

4.2、信号与槽机制(核心功能)

4.2.1  内置信号与槽

4.2.2  自定义信号与槽

4.3、高级功能

1. 动态属性系统

2. 定时器功能

4.4、事件系统(QObject 的底层机制)

4.5 案例:PyQt5 定时器功能演示

4.5.1 定时器基础类(MyObject)

4.5.2  倒计时标签(MyLabel)

4.5.3 自动缩放窗口(MyWidget)

4.5.4 主程序流程

4.5.5 关键知识点

4.6 PyQt5 定时器的其他常见应用场景

4.6.1 表单输入验证延迟

4.6.2 自动保存功能

4.6.3  进度条平滑更新

4.6.4 轮播图 / 自动切换视图

4.6.5  模拟网络请求延迟

4.6.6  键盘重复延迟控制

4.6.7 限时操作

​编辑



前言

        PyQt5 是一个用于创建图形用户界面(GUI)的 Python 绑定库,它封装了 Qt 框架(C++)的强大功能,使开发者能够在 Python 环境中轻松构建跨平台的应用程序。作为 Qt 5 的 Python 接口,PyQt5 提供了超过 1000 个类,支持丰富的 GUI 组件(如窗口、按钮、菜单、对话框等)、多媒体处理、网络编程、数据库交互等功能。其主要特点包括:跨平台兼容性(Windows、macOS、Linux 等)、信号与槽机制实现高效事件处理、内置多种布局管理器简化界面设计、支持自定义控件和样式表美化界面,同时提供 Qt Designer 可视化工具快速创建 UI 界面。PyQt5 广泛应用于桌面应用开发、数据可视化、游戏开发、工业控制等领域,是 Python 开发者首选的 GUI 框架之一。


 一、软件准备

python版本:3.8

PyQt5模块安装:

pip install PyQt5 PyQt5-tools -i https://pypi.tuna.tsinghua.edu.cn/simple

验证是否安装成功

import sys
from PyQt5.QtWidgets import QApplication, QLabelapp = QApplication(sys.argv)
window = QLabel("Hello PyQt5!")
window.show()
sys.exit(app.exec_())

二、初体验

from PyQt5.Qt import *
import sysapp = QApplication(sys.argv)window = QWidget()
window.setWindowTitle("PyQt5初体验")
window.resize(500, 500)
window.move(400, 200)label = QLabel(window)
label.setText("Hello World")
label.move(200, 200)window.show()sys.exit(app.exec_())

 

同样可以将固定的程序代码保存到实时模板,通过缩写方式快速调用:

三、程序结构分析

3.1、PyQt5 应用程序基本结构

        PyQt5 应用程序遵循 "创建控件→设置属性→显示控件→进入事件循环" 的基本流程。以下是一个简单示例的完整分析:

# 0. 导入需要的包和模块
from PyQt5.Qt import *  # 主要包含了我们常用的一些类, 汇总到了一块
import sys# 1. 创建一个应用程序对象
app = QApplication(sys.argv)# 2. 控件的操作
# 创建控件,设置控件(大小,位置,样式...),事件,信号的处理# 2.1 创建控件
# 当我们创建一个控件之后, 如果说,这个控件没有父控件, 则把它当做顶层控件(窗口)
# 系统会自动的给窗口添加一些装饰(标题栏), 窗口控件具备一些特性(设置标题,图标)
window = QWidget()
# window = QPushButton()
# window = QLabel()# 2.2 设置控件
# window.setText("hello World")
window.setWindowTitle("PyQt5")
window.resize(400, 400)# 控件也可以作为一个容器(承载其他的控件)
label = QLabel(window)
label.setText("xxx")
label.setWindowTitle("xxxxxxx")
label.move(100, 50)
# label.show()# 2.3 展示控件
# 刚创建好一个控件之后,(这个控件没有什么父控件), 默认情况下不会被展示,只有手动的调用show()才可以
# 如果说这个控件, 有父控件的,那么一般情况下, 父控件展示之后, 子控件会自动展示
window.show()# 3. 应用程序的执行, 进入到消息循环
# 让整个程序开始执行,并且进入到消息循环(无限循环)
# 检测整个程序所接收到的用户的交互信息
sys.exit(app.exec_())

3.2、关键组件解析

  1. 应用程序对象(QApplication)

    • 每个 PyQt5 应用必须有且仅有一个 QApplication 对象
    • 负责处理应用程序的初始化、事件处理、资源管理等
    • sys.argv参数用于传递命令行参数给应用程序
  2. 控件(Widgets)

    • 顶层控件(窗口):没有父控件的控件会被视为顶层窗口
      • 系统自动添加标题栏、边框等装饰
      • 常用方法:setWindowTitle()resize()setGeometry()
    • 子控件:指定了父控件的控件,会显示在父控件内部
      • 位置和大小相对于父控件
      • 父控件显示时,子控件自动显示
  3. 常见控件类

    • QWidget:基础控件类,可作为容器
    • QLabel:显示文本或图像
    • QPushButton:按钮控件
    • QLineEdit:单行文本输入框
    • QTextEdit:多行文本编辑框
    • QComboBox:下拉选择框
    • QCheckBox:复选框
    • QRadioButton:单选按钮
  4. 控件位置与大小

    • resize(width, height):设置控件大小
    • move(x, y):设置控件位置(左上角坐标)
    • setGeometry(x, y, width, height):同时设置位置和大小
    • 坐标系统:原点 (0,0) 位于窗口 / 控件的左上角

3.3、事件循环机制

  • app.exec_():启动应用程序的事件循环
    • 持续监听用户交互(如鼠标点击、键盘输入等)
    • 将事件分发给相应的控件处理
  • sys.exit():确保应用程序干净退出,释放资源

3.4、注意事项

  1. 中文显示问题

    • PyQt5 默认支持中文,但某些系统可能需要指定字体
    font = QFont("SimHei")  # 指定中文字体
    app.setFont(font)
    
  2. 控件层级关系

    • 子控件必须在父控件显示前创建并设置好属性
    • 父控件隐藏时,子控件也会被隐藏
  3. 窗口关闭行为

    • 默认情况下,关闭最后一个顶层窗口会导致应用程序退出
    • 可通过app.setQuitOnLastWindowClosed(False)修改此行为

四、QObject API

        QObject 是 PyQt5 中所有类的基类,提供了 PyQt5 的核心功能,如信号与槽机制、对象树管理、事件系统等。理解 QObject 是掌握 PyQt5 的关键。 

4.1、QObject 基础概念

4.1.1 对象树与内存管理

QObject 使用对象树来管理内存,就像公司的组织结构图:每个部门(父对象)下有多个员工(子对象),当部门被撤销时,员工也会被自动解雇。

案例:创建父子对象

from PyQt5.QtCore import QObject# 创建一个"部门经理"对象
manager = QObject()
manager.setObjectName("Manager")  # 设置对象名称# 创建两个"员工"对象,属于manager
employee1 = QObject(manager)
employee1.setObjectName("Employee1")employee2 = QObject(manager)
employee2.setObjectName("Employee2")# 查看manager的"下属"
print(manager.children())  # 输出: [<PyQt5.QtCore.QObject object at 0x00000199E38B5E50>, <PyQt5.QtCore.QObject object at 0x00000199E38B5EE0>]# 当manager被删除时,employee1和employee2会被自动删除

4.1.2 对象命名与查找

每个 QObject 都可以有一个名称,就像每个人都有自己的名字。可以通过名称查找对象,就像在公司通讯录里按名字找人。

案例:对象查找

# 继续上面的案例
# 通过名称查找employee2
found = manager.findChild(QObject, "Employee2")
print(f"找到对象: {found.objectName()}")  # 输出: Employee2

4.2、信号与槽机制(核心功能)

信号与槽是 QObject 的核心功能,用于对象间的通信。可以把它想象成 "广播电台":

信号:电台发出的广播(如新闻、音乐)

:听众的收音机(接收特定广播并作出反应)

连接:收音机调谐到特定频率

4.2.1  内置信号与槽

PyQt5 的控件自带许多信号,如按钮的clicked信号。

案例:按钮点击事件

from PyQt5.QtWidgets import QApplication, QPushButton
import sysapp = QApplication(sys.argv)# 创建一个"按钮",就像一个"门铃"
button = QPushButton("按门铃")# 定义一个"槽"函数,就像"门铃响了,主人来开门"
def door_opened():print("门开了!")# 连接"信号"和"槽",就像把"门铃"和"主人"连接起来
button.clicked.connect(door_opened)button.show()
sys.exit(app.exec_())

 

4.2.2  自定义信号与槽

除了内置信号,还可以创建自定义信号,就像创建自己的电台节目。

案例:自定义信号

from PyQt5.QtCore import QObject, pyqtSignalclass Cook(QObject):# 创建一个"菜做好了"的信号,就像电台的"节目开始"信号dish_ready = pyqtSignal(str)def cook_dish(self, dish_name):print(f"厨师正在做: {dish_name}")# 发出"菜做好了"的信号,并传递菜名self.dish_ready.emit(dish_name)class Waiter(QObject):# 定义一个"上菜"的槽函数,就像听众听到节目后的反应def serve_dish(self, dish):print(f"服务员: 这是您点的 {dish},请慢用!")# 创建厨师和服务员对象
chef = Cook()
waiter = Waiter()# 连接信号和槽,就像把电台和收音机连接起来
chef.dish_ready.connect(waiter.serve_dish)# 厨师开始做菜
chef.cook_dish("宫保鸡丁")

         运行 PyQt5 代码时看到了 sipPyTypeDict() is deprecated 警告,这是因为 PyQt5 的底层库 sip 更新后,旧的 API 不再推荐使用。这类警告不影响代码运行,但需要及时修复以确保兼容性。

# 忽略sip弃用警告
warnings.filterwarnings("ignore", message="sipPyTypeDict")

完整代码:

import warnings
from PyQt5.QtCore import QObject, pyqtSignal# 忽略sip弃用警告
warnings.filterwarnings("ignore", message="sipPyTypeDict")class Cook(QObject):dish_ready = pyqtSignal(str)def cook_dish(self, dish_name):print(f"厨师正在做: {dish_name}")self.dish_ready.emit(dish_name)class Waiter(QObject):def serve_dish(self, dish):print(f"服务员: 这是您点的 {dish},请慢用!")chef = Cook()
waiter = Waiter()
chef.dish_ready.connect(waiter.serve_dish)
chef.cook_dish("宫保鸡丁")

4.3、高级功能

1. 动态属性系统

QObject 支持动态添加属性,就像给人添加额外的标签。

案例:动态属性

from PyQt5.QtCore import QObjectperson = QObject()
# 给person添加"姓名"和"年龄"属性
person.setProperty("name", "张三")
person.setProperty("age", 30)# 获取属性值
print(f"{person.property('name')} 的年龄是 {person.property('age')}")

 

2. 定时器功能

QObject 提供定时器功能,就像设置闹钟提醒自己。

案例:定时器

from PyQt5.QtCore import QObject, QTimer
import sys
import warnings# 忽略sip弃用警告
warnings.filterwarnings("ignore", message="sipPyTypeDict")
class Alarm(QObject):def __init__(self):super().__init__()self.timer = QTimer(self)  # 创建一个定时器self.timer.timeout.connect(self.ring)  # 连接超时信号到ring方法self.count = 0def start(self, seconds):print(f"设置闹钟,{seconds}秒后响铃")self.timer.start(seconds * 1000)  # 设置定时器间隔(毫秒)def ring(self):self.count += 1print(f"闹钟响了!第 {self.count} 次提醒")if self.count >= 3:  # 响铃3次后停止self.timer.stop()print("闹钟停止")sys.exit()  # 退出程序# 创建闹钟并启动
alarm = Alarm()
alarm.start(2)  # 设置2秒后响铃

 

4.4、事件系统(QObject 的底层机制)

QObject 的事件系统是信号与槽的底层实现,可以把它想象成 "公司邮件系统":

事件:公司内部的各种邮件(如会议通知、任务分配)

事件处理:员工接收邮件并执行相应操作

案例:重写事件处理函数

from PyQt5.QtWidgets import QApplication, QPushButton
import sys
import warnings# 忽略sip弃用警告
warnings.filterwarnings("ignore", message="sipPyTypeDict")
class MyButton(QPushButton):# 重写鼠标点击事件处理函数def mousePressEvent(self, event):print("自定义按钮: 鼠标被按下了!")# 调用父类的事件处理函数,保持默认行为super().mousePressEvent(event)app = QApplication(sys.argv)
button = MyButton("自定义按钮")
button.show()
sys.exit(app.exec_())

4.5 案例:PyQt5 定时器功能演示

        这段代码使用 PyQt5 创建了一个图形界面应用,主要演示了 QObject 的定时器功能。通过三个自定义类,分别实现了不同的定时效果:

# 0. 导入需要的包和模块
from PyQt5.Qt import * 
import sys
import warningsimport warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, message="sipPyTypeDict")
class MyObject(QObject):def timerEvent(self, evt):print(evt, "1")class MyLabel(QLabel):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.setText("10")self.move(100, 100)self.setStyleSheet("font-size: 22px;")def setSec(self, sec):self.setText(str(sec))def startMyTimer(self, ms):self.timer_id = self.startTimer(ms)def timerEvent(self, *args, **kwargs):print("xx")# 1. 获取当前的标签的内容current_sec = int(self.text())current_sec -= 1self.setText(str(current_sec))if current_sec == 0:print("停止")self.killTimer(self.timer_id)class MyWidget(QWidget):def timerEvent(self, *args, **kwargs):current_w = self.width()current_h = self.height()self.resize(current_w + 10, current_h + 10)# 1. 创建一个应用程序对象
app = QApplication(sys.argv)# 2. 控件的操作
# 2.1 创建控件
window = MyWidget()
# 2.2 设置控件
window.setWindowTitle("QObject定时器的使用")
window.resize(500, 500)window.startTimer(100)# label = MyLabel(window)
# label.setSec(5)
# label.startMyTimer(500)# 2.3 展示控件
window.show()
# 3. 应用程序的执行, 进入到消息循环
sys.exit(app.exec_())

代码解读

4.5.1 定时器基础类(MyObject)

class MyObject(QObject):def timerEvent(self, evt):print(evt, "1")

继承自 QObject,是 PyQt5 中所有对象的基类

timerEvent是定时器事件处理函数,当定时器触发时自动调用

功能:每次定时器触发时打印事件对象和数字 1

4.5.2  倒计时标签(MyLabel)

class MyLabel(QLabel):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.setText("10")  # 初始显示10self.move(100, 100)  # 位置self.setStyleSheet("font-size: 22px;")  # 字体大小def setSec(self, sec):self.setText(str(sec))  # 设置倒计时秒数def startMyTimer(self, ms):self.timer_id = self.startTimer(ms)  # 启动定时器,参数为毫秒def timerEvent(self, *args, **kwargs):current_sec = int(self.text())  # 获取当前显示的秒数current_sec -= 1  # 减1self.setText(str(current_sec))  # 更新显示if current_sec == 0:  # 倒计时结束print("停止")self.killTimer(self.timer_id)  # 停止定时器

继承自 QLabel(标签控件)

功能:实现一个倒计时标签,从指定秒数开始递减,到 0 时停止

关键方法:

  • startTimer(ms):启动定时器,每ms毫秒触发一次
  • timerEvent():定时器触发时执行的代码
  • killTimer(timer_id):停止指定的定时器

4.5.3 自动缩放窗口(MyWidget)

class MyWidget(QWidget):def timerEvent(self, *args, **kwargs):current_w = self.width()current_h = self.height()self.resize(current_w + 10, current_h + 10)  # 每次增加10x10像素

继承自 QWidget(窗口控件)

功能:窗口会随着时间自动变大

定时器触发时,获取当前窗口宽高并各增加 10 像素

4.5.4 主程序流程

# 1. 创建应用程序对象(必须)
app = QApplication(sys.argv)# 2. 创建并设置窗口
window = MyWidget()  # 使用自定义的自动缩放窗口
window.setWindowTitle("QObject定时器的使用")
window.resize(500, 500)# 启动窗口的定时器,每100毫秒触发一次
window.startTimer(100)# 3. 显示窗口
window.show()# 4. 进入应用程序主循环(保持窗口打开)
sys.exit(app.exec_())

4.5.5 关键知识点

  1. QObject 定时器机制

    • 通过startTimer(ms)启动定时器,返回定时器 ID
    • 重写timerEvent()方法处理定时器触发事件
    • 使用killTimer(id)停止定时器
  2. 多定时器管理

    • 一个对象可以有多个定时器,每个定时器有唯一 ID
    • 通过不同 ID 管理不同定时器的开关
  3. 控件继承与扩展

    • 继承现有控件(如 QLabel、QWidget)
    • 添加自定义功能(如倒计时、自动缩放)

代码未使用部分

# 被注释掉的代码:创建倒计时标签
# label = MyLabel(window)
# label.setSec(5)
# label.startMyTimer(500)

如果取消注释,窗口中会显示一个倒计时标签,从 5 秒开始倒计时,每秒减 1,到 0 时停止。

这段代码通过三个例子展示了 PyQt5 中定时器的基本用法:

  1. 基础定时器事件处理(MyObject)
  2. 倒计时功能实现(MyLabel)
  3. 动态窗口缩放(MyWidget)

定时器是 GUI 编程中常用的功能,常用于动画效果、数据刷新、定时任务等场景。

QObject 是 PyQt5 的核心基础,主要提供:

  1. 对象树管理:自动内存回收(部门 - 员工模型)
  2. 信号与槽机制:对象间通信(电台 - 收音机模型)
  3. 动态属性系统:灵活添加对象属性(给人贴标签)
  4. 定时器功能:周期性执行任务(设置闹钟)
  5. 事件系统:处理各种系统事件(公司邮件系统)

掌握 QObject API 是深入学习 PyQt5 的关键,通过上述案例,应该能理解 QObject 的基本概念和核心功能。

4.6 PyQt5 定时器的其他常见应用场景

4.6.1 表单输入验证延迟

场景:用户输入表单内容时,延迟验证以减少频繁计算
效果:用户停止输入一段时间后才触发验证逻辑

from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QVBoxLayout, QLabel
from PyQt5.QtCore import QTimer
import sysclass Form(QWidget):def __init__(self):super().__init__()# 设置窗口基本属性self.setWindowTitle("输入延迟验证示例")self.setMinimumWidth(400)self.setMinimumHeight(150)# 创建输入框和提示标签self.input = QLineEdit(self)self.input.setPlaceholderText("输入至少3个字符...")self.tip_label = QLabel("输入内容将在停止输入后验证", self)self.tip_label.setStyleSheet("color: gray; font-size: 12px;")# 初始化定时器IDself.timer_id = None# 连接信号与槽self.input.textChanged.connect(self.start_validation_timer)# 设置布局self.setup_layout()def setup_layout(self):"""设置窗口布局"""layout = QVBoxLayout(self)layout.addWidget(self.input)layout.addWidget(self.tip_label)layout.setSpacing(20)  # 设置控件间距layout.setContentsMargins(50, 50, 50, 50)  # 设置边距self.setLayout(layout)def start_validation_timer(self):"""启动或重置验证定时器"""# 如果已有定时器,先停止if self.timer_id:self.killTimer(self.timer_id)# 启动新定时器,延迟500msself.timer_id = self.startTimer(500)# 更新提示信息self.tip_label.setText("正在等待输入...")def timerEvent(self, event):"""定时器触发时执行验证逻辑"""text = self.input.text()if len(text) < 3:self.input.setStyleSheet("color: red")self.tip_label.setText("输入太短,至少需要3个字符")else:self.input.setStyleSheet("color: black")self.tip_label.setText("输入有效 ✔")# 验证完成后停止定时器self.killTimer(self.timer_id)self.timer_id = None# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = Form()window.show()sys.exit(app.exec_())

 

         这段代码实现了一个带有延迟验证功能的表单输入框,核心是通过定时器实现 "用户停止输入一段时间后才进行验证" 的效果。这样可以避免在用户输入过程中频繁触发验证逻辑,提升性能和用户体验。

1. 整体工作流程

用户输入文本 → 每次文本变化时重置定时器 → 定时器触发时执行验证逻辑

2. 关键变量和方法

self.timer_id = None  # 存储定时器ID,用于管理定时器# 文本变化信号连接到启动定时器的方法
self.input.textChanged.connect(self.start_validation_timer)def start_validation_timer(self):# 停止已有的定时器(如果存在)if self.timer_id:self.killTimer(self.timer_id)# 启动新定时器,设置500ms延迟self.timer_id = self.startTimer(500)def timerEvent(self, event):# 定时器触发时执行验证逻辑text = self.input.text()if len(text) < 3:self.input.setStyleSheet("color: red")  # 验证失败,文字变红else:self.input.setStyleSheet("color: black")  # 验证成功,文字变黑# 验证完成后停止定时器self.killTimer(self.timer_id)self.timer_id = None

为什么这样设计?

1. 为什么每次文本变化都要重置定时器?

如果用户连续输入(例如快速打字),每次输入都会触发textChanged信号

每次信号触发时重置定时器,确保只有当用户停止输入超过 500ms 时才会执行验证

效果:用户快速输入时不会频繁验证,停止输入后才验证最终结果

2. 为什么需要在timerEvent中停止定时器?

验证逻辑只需要执行一次

执行后停止定时器,等待下一次文本变化重新启动定时器

如果不停止,定时器会持续触发(每 500ms 一次),导致不必要的验证

实际应用场景

1. 搜索建议

  • 用户输入搜索关键词时,延迟触发搜索建议
  • 避免用户还在输入过程中就频繁请求服务器

2. 密码强度验证

  • 用户输入密码时,延迟显示密码强度提示
  • 减少实时计算密码强度的性能开销

3. 表单字段格式验证

  • 验证邮箱、手机号等格式时,延迟验证避免干扰用户输入
  • 例如:用户输入邮箱时,不需要每个字符都验证,输入完成后验证一次即可

优化建议

1. 添加验证状态提示

def timerEvent(self, event):text = self.input.text()if len(text) == 0:self.input.setStyleSheet("color: gray")  # 空文本显示灰色elif len(text) < 3:self.input.setStyleSheet("color: red")   # 太短显示红色else:self.input.setStyleSheet("color: green") # 符合要求显示绿色self.killTimer(self.timer_id)self.timer_id = None

2. 提取验证逻辑为单独方法

def validate_input(self):text = self.input.text()if len(text) < 3:return Falsereturn Truedef timerEvent(self, event):if self.validate_input():self.input.setStyleSheet("color: black")else:self.input.setStyleSheet("color: red")self.killTimer(self.timer_id)self.timer_id = None

3. 支持自定义验证条件

def __init__(self, min_length=3):super().__init__()self.min_length = min_length# 其他代码保持不变def validate_input(self):return len(self.input.text()) >= self.min_length

        这种延迟验证的技术在现代 GUI 应用中非常常见,它平衡了验证的实时性和性能开销,既能及时反馈用户输入的有效性,又不会因为频繁验证而影响用户体验。

4.6.2 自动保存功能

场景:编辑器自动保存文档,避免意外丢失
效果:每隔一段时间自动保存当前内容

from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QMenuBar, QMenu, QAction, QMessageBox
from PyQt5.QtCore import QTimer, QFile, QTextStream
import sys
import osclass Editor(QTextEdit):def __init__(self):super().__init__()# 初始化变量self.file_path = None  # 当前文件路径self.changed = False   # 内容是否修改self.auto_save_interval = 30000  # 自动保存间隔(毫秒)# 连接文本变化信号self.textChanged.connect(self.on_text_changed)# 启动自动保存定时器self.timer_id = self.startTimer(self.auto_save_interval)def on_text_changed(self):"""文本内容变化时标记为已修改"""self.changed = Truedef timerEvent(self, event):"""定时器触发时执行自动保存"""if self.changed:# 实际应用中这里会执行保存逻辑if self.file_path and os.path.exists(self.file_path):self.save_file(self.file_path)else:# 新文件或未保存过的文件不自动保存passself.changed = False  # 重置修改标记print(f"自动保存成功 - {self.get_current_time()}")def get_current_time(self):"""获取当前时间字符串"""from datetime import datetimereturn datetime.now().strftime("%Y-%m-%d %H:%M:%S")def save_file(self, path):"""保存文件到指定路径"""file = QFile(path)if file.open(QFile.WriteOnly | QFile.Text):stream = QTextStream(file)stream << self.toPlainText()file.close()self.changed = False  # 保存后重置标记return Truereturn Falsedef save_as(self):"""另存为新文件"""from PyQt5.QtWidgets import QFileDialogpath, _ = QFileDialog.getSaveFileName(self, "另存为", "", "文本文件 (*.txt);;所有文件 (*)")if path:self.file_path = pathreturn self.save_file(path)return Falsedef save(self):"""保存文件(存在则覆盖,不存在则调用另存为)"""if self.file_path:return self.save_file(self.file_path)else:return self.save_as()class MainWindow(QMainWindow):def __init__(self):super().__init__()# 设置窗口self.setWindowTitle("自动保存文本编辑器")self.setMinimumSize(800, 600)# 创建文本编辑组件self.editor = Editor()self.setCentralWidget(self.editor)# 创建菜单栏self.create_menu()# 状态栏self.statusBar().showMessage("就绪")def create_menu(self):"""创建菜单栏和菜单选项"""menubar = QMenuBar(self)# 文件菜单file_menu = QMenu("文件", self)menubar.addMenu(file_menu)# 新建文件new_action = QAction("新建", self)new_action.setShortcut("Ctrl+N")new_action.triggered.connect(self.new_file)file_menu.addAction(new_action)# 打开文件open_action = QAction("打开", self)open_action.setShortcut("Ctrl+O")open_action.triggered.connect(self.open_file)file_menu.addAction(open_action)# 保存文件save_action = QAction("保存", self)save_action.setShortcut("Ctrl+S")save_action.triggered.connect(self.editor.save)file_menu.addAction(save_action)# 另存为save_as_action = QAction("另存为", self)save_as_action.triggered.connect(self.editor.save_as)file_menu.addAction(save_as_action)# 分隔线file_menu.addSeparator()# 退出exit_action = QAction("退出", self)exit_action.setShortcut("Ctrl+Q")exit_action.triggered.connect(self.close)file_menu.addAction(exit_action)# 设置菜单栏self.setMenuBar(menubar)def new_file(self):"""新建文件"""if self.editor.changed:reply = QMessageBox.question(self, "提示", "文件已修改,是否保存?",QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)if reply == QMessageBox.Yes:self.editor.save()elif reply == QMessageBox.Cancel:returnself.editor.clear()self.editor.file_path = Noneself.editor.changed = Falseself.statusBar().showMessage("新建文件")def open_file(self):"""打开文件"""from PyQt5.QtWidgets import QFileDialogif self.editor.changed:reply = QMessageBox.question(self, "提示", "文件已修改,是否保存?",QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)if reply == QMessageBox.Yes:if not self.editor.save():returnelif reply == QMessageBox.Cancel:returnpath, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "文本文件 (*.txt);;所有文件 (*)")if path:file = QFile(path)if file.open(QFile.ReadOnly | QFile.Text):stream = QTextStream(file)self.editor.setText(stream.readAll())file.close()self.editor.file_path = pathself.editor.changed = Falseself.statusBar().showMessage(f"打开文件: {os.path.basename(path)}")# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = MainWindow()window.show()sys.exit(app.exec_())

 

代码解读

1. 核心自动保存机制(Editor 类

状态标记:通过changed变量标记文本是否被修改

定时器控制:每 30 秒(30000 毫秒)触发一次自动保存

保存逻辑:检测到内容修改时,对已保存过的文件自动覆盖保存

时间显示:保存时打印当前时间,便于查看保存记录

2. 文件操作功能

保存文件save_file()方法实现文本内容写入文件

另存为save_as()方法打开文件对话框选择保存路径

新建文件new_file()方法处理新建文件时的未保存内容提示

打开文件open_file()方法实现文件读取和内容加载

3. 界面交互设计(MainWindow 类)

菜单栏:包含 "新建"、"打开"、"保存"、"另存为"、"退出" 等选项

快捷键:支持常用快捷键(Ctrl+N/S/O/Q)

状态栏:显示当前操作状态和文件信息

提示对话框:在可能丢失数据的操作前显示确认对话框

4. 用户体验优化

修改标记:仅在内容修改时执行保存,避免无意义的文件写入

智能提示:新建或打开文件时检测未保存修改,防止数据丢失

时间记录:自动保存时打印时间,方便用户了解保存频率

文件路径管理:通过file_path变量跟踪当前文件路径

运行效果与交互流程

  1. 启动程序

    • 显示空白文本编辑窗口
    • 状态栏显示 "就绪"
    • 自动启动 30 秒一次的自动保存定时器
  2. 文本编辑

    • 输入文本时,changed标记设为 True
    • 每 30 秒自动保存一次(控制台打印 "自动保存成功")
  3. 文件操作

    • 按 Ctrl+S 或点击 "保存" 手动保存文件
    • 点击 "另存为" 可将文件保存到新路径
    • 新建或打开文件时,会提示保存未修改内容
  4. 退出程序

    • 若有未保存修改,显示提示对话框
    • 选择 "是" 则保存后退出,选择 "否" 则直接退出,选择 "取消" 则取消退出

技术要点说明

  1. 定时器与事件处理

    • 通过startTimer()启动定时器,返回定时器 ID
    • 重写timerEvent()方法处理定时触发事件
    • 使用killTimer()可停止定时器(示例中未使用,但可在需要时添加)
  2. Qt 文件操作

    • 使用QFileQTextStream进行文件读写
    • 文本编辑内容通过toPlainText()获取,通过setText()设置
  3. 信号与槽机制

    • textChanged信号连接到on_text_changed槽函数
    • 菜单选项的triggered信号连接到对应的功能方法
  4. 用户交互设计

    • 使用QMessageBox显示提示对话框
    • 通过QFileDialog实现文件选择对话框
    • 状态栏statusBar()显示操作状态信息

扩展应用场景

  1. 多文档支持:修改代码支持同时打开多个文本编辑窗口
  2. 格式支持:添加对富文本 (RTF)、Markdown 等格式的支持
  3. 版本历史:实现自动保存时创建文件历史版本
  4. 云端同步:结合定时器实现自动同步到云端存储
  5. 字数统计:在状态栏显示当前文档字数统计信息

        这个完整的自动保存文本编辑器示例,展示了定时器在实际应用中的典型场景,通过合理的代码组织和用户体验设计,实现了实用的自动保存功能,同时保持了良好的交互性和可维护性。

4.6.3  进度条平滑更新

场景:显示长时间任务的进度,避免界面卡顿
效果:进度条平滑增长,提升用户体验

from PyQt5.QtWidgets import QApplication, QWidget, QProgressBar, QPushButton, QVBoxLayout, QLabel
from PyQt5.QtCore import QTimer
import sysclass ProgressDemo(QWidget):def __init__(self):super().__init__()# 设置窗口属性self.setWindowTitle("进度条演示")self.setGeometry(300, 300, 400, 200)# 创建进度条self.progress = QProgressBar(self)self.progress.setRange(0, 100)  # 进度范围0-100%self.progress.setValue(0)  # 初始值为0# 创建状态标签self.status_label = QLabel("准备就绪", self)# 创建控制按钮self.start_btn = QPushButton("开始", self)self.start_btn.clicked.connect(self.start_progress)self.reset_btn = QPushButton("重置", self)self.reset_btn.clicked.connect(self.reset_progress)self.reset_btn.setEnabled(False)  # 初始禁用重置按钮# 布局设置layout = QVBoxLayout()layout.addWidget(self.progress)layout.addWidget(self.status_label)layout.addWidget(self.start_btn)layout.addWidget(self.reset_btn)self.setLayout(layout)# 进度条参数self.total_steps = 1000  # 模拟总步数self.current_step = 0  # 当前步数self.timer_id = None  # 定时器IDdef start_progress(self):"""开始进度条更新"""if self.timer_id is None:# 禁用开始按钮,启用重置按钮self.start_btn.setEnabled(False)self.reset_btn.setEnabled(True)self.status_label.setText("处理中...")# 启动定时器,每50ms更新一次进度条self.timer_id = self.startTimer(50)def reset_progress(self):"""重置进度条"""if self.timer_id:self.killTimer(self.timer_id)self.timer_id = None# 重置进度条和状态self.current_step = 0self.progress.setValue(0)self.status_label.setText("准备就绪")# 启用开始按钮,禁用重置按钮self.start_btn.setEnabled(True)self.reset_btn.setEnabled(False)def timerEvent(self, event):"""定时器触发时更新进度条"""if self.current_step < self.total_steps:self.current_step += 10# 计算进度百分比percent = int(self.current_step / self.total_steps * 100)self.progress.setValue(percent)self.status_label.setText(f"处理中... {percent}%")else:# 完成后停止定时器self.killTimer(self.timer_id)self.timer_id = Noneself.progress.setValue(100)self.status_label.setText("任务完成!")self.reset_btn.setEnabled(True)print("任务完成")# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = ProgressDemo()window.show()sys.exit(app.exec_())

 

代码功能模块解读

1. 界面组件与布局

进度条QProgressBar显示任务完成百分比

状态标签:动态显示当前进度状态(准备就绪、处理中、任务完成)

控制按钮

  • 开始按钮:启动进度条更新
  • 重置按钮:重置进度条到初始状态

2. 进度条控制机制

定时器管理:通过startTimer(50)创建 50ms 间隔的定时器

进度计算current_step累加,根据total_steps计算百分比

平滑更新:每次增加 10 个单位,使进度条平滑增长

3. 状态管理

按钮状态

  • 开始后禁用 "开始" 按钮,启用 "重置" 按钮
  • 完成后仅启用 "重置" 按钮

状态文本:实时更新标签显示当前进度状态

4. 事件处理

定时器事件timerEvent()处理定时更新逻辑

按钮点击事件

  • start_progress():启动定时器
  • reset_progress():重置进度和状态

运行效果与交互流程

  1. 初始状态

    • 进度条值为 0%
    • 状态标签显示 "准备就绪"
    • "开始" 按钮可用,"重置" 按钮禁用
  2. 点击 "开始" 按钮后

    • 开始按钮禁用,重置按钮启用
    • 进度条开始平滑增长(每 50ms 更新一次)
    • 状态标签显示当前进度百分比(如 "处理中... 35%")
  3. 进度达到 100% 后

    • 进度条停在 100%
    • 状态标签显示 "任务完成!"
    • 仅重置按钮可用
  4. 点击 "重置" 按钮后

    • 进度条重置为 0%
    • 恢复初始状态(开始按钮可用,重置按钮禁用)

技术要点说明

  1. 定时器机制

    • startTimer(interval):启动定时器,返回定时器 ID
    • timerEvent(event):定时器触发时自动调用的回调函数
    • killTimer(timer_id):停止指定定时器
  2. 进度条控制

    • setRange(min, max):设置进度条范围
    • setValue(value):设置当前进度值
    • 百分比计算:percent = (current / total) * 100
  3. 界面交互优化

    • 按钮状态管理:防止重复点击
    • 状态文本提示:提供明确的用户反馈
    • 平滑动画效果:通过适当的定时器间隔和步长控制

扩展应用场景

  1. 文件下载进度:显示网络文件下载百分比
  2. 数据处理进度:如大型数据集的分析进度
  3. 安装程序:软件安装过程的进度显示
  4. 游戏加载:游戏资源加载进度
  5. 批量操作:如批量图片处理、文件转换等

        这个完整的进度条演示示例,展示了如何使用定时器实现平滑的进度更新效果,通过合理的状态管理和用户交互设计,提供了直观且友好的进度展示界面。

4.6.4 轮播图 / 自动切换视图

场景:图片轮播、广告展示等需要自动切换的场景
效果:定时切换显示内容

from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QVBoxLayout,QHBoxLayout, QPushButton, QFileDialog)
from PyQt5.QtGui import QPixmap, QPalette, QColor
from PyQt5.QtCore import Qt, QTimer
import sys
import osclass Carousel(QWidget):def __init__(self):super().__init__()# 设置窗口属性self.setWindowTitle("图片轮播")self.setMinimumSize(600, 400)# 图片列表和当前索引self.images = []  # 存储图片路径self.current_index = 0# 创建图片显示区域self.image_label = QLabel(self)self.image_label.setAlignment(Qt.AlignCenter)self.image_label.setMinimumSize(500, 300)self.image_label.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")# 创建状态标签self.status_label = QLabel("无图片", self)self.status_label.setAlignment(Qt.AlignCenter)# 创建控制按钮self.prev_btn = QPushButton("上一张", self)self.prev_btn.clicked.connect(self.prev_image)self.prev_btn.setEnabled(False)  # 初始禁用self.next_btn = QPushButton("下一张", self)self.next_btn.clicked.connect(self.next_image)self.next_btn.setEnabled(False)  # 初始禁用self.start_stop_btn = QPushButton("开始轮播", self)self.start_stop_btn.clicked.connect(self.toggle_carousel)self.start_stop_btn.setEnabled(False)  # 初始禁用self.load_btn = QPushButton("加载图片", self)self.load_btn.clicked.connect(self.load_images)# 按钮布局btn_layout = QHBoxLayout()btn_layout.addWidget(self.prev_btn)btn_layout.addWidget(self.next_btn)btn_layout.addWidget(self.start_stop_btn)btn_layout.addWidget(self.load_btn)# 主布局main_layout = QVBoxLayout()main_layout.addWidget(self.image_label)main_layout.addWidget(self.status_label)main_layout.addLayout(btn_layout)self.setLayout(main_layout)# 定时器设置self.timer = QTimer(self)self.timer.timeout.connect(self.next_image)self.is_running = False  # 轮播状态标记def load_images(self):"""加载图片"""file_paths, _ = QFileDialog.getOpenFileNames(self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp)")if file_paths:self.images = file_pathsself.current_index = 0self.update_image()# 更新按钮状态self.prev_btn.setEnabled(True)self.next_btn.setEnabled(True)self.start_stop_btn.setEnabled(True)# 如果正在运行,重启定时器if self.is_running:self.timer.stop()self.timer.start(3000)self.status_label.setText(f"已加载 {len(self.images)} 张图片")def update_image(self):"""更新显示的图片"""if not self.images:returnimage_path = self.images[self.current_index]pixmap = QPixmap(image_path)# 调整图片大小以适应标签scaled_pixmap = pixmap.scaled(self.image_label.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation)self.image_label.setPixmap(scaled_pixmap)self.status_label.setText(f"图片 {self.current_index + 1}/{len(self.images)}: {os.path.basename(image_path)}")def next_image(self):"""显示下一张图片"""if not self.images:returnself.current_index = (self.current_index + 1) % len(self.images)self.update_image()def prev_image(self):"""显示上一张图片"""if not self.images:returnself.current_index = (self.current_index - 1) % len(self.images)self.update_image()def toggle_carousel(self):"""开始/停止轮播"""if self.is_running:self.timer.stop()self.start_stop_btn.setText("开始轮播")self.status_label.setText(f"轮播已暂停 - 图片 {self.current_index + 1}/{len(self.images)}")else:if self.images:self.timer.start(3000)  # 每3秒切换一次self.start_stop_btn.setText("停止轮播")self.status_label.setText(f"轮播中 - 图片 {self.current_index + 1}/{len(self.images)}")self.is_running = not self.is_runningdef resizeEvent(self, event):"""窗口大小改变时调整图片"""super().resizeEvent(event)if self.images:self.update_image()# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = Carousel()window.show()sys.exit(app.exec_())

 

代码功能模块解读

1. 界面组件与布局

图片显示区域QLabel用于展示图片,设置了居中对齐和背景样式

状态标签:显示当前图片索引和文件名

控制按钮

  • 上一张 / 下一张:手动切换图片
  • 开始 / 停止轮播:控制自动切换
  • 加载图片:打开文件对话框选择图片

2. 图片管理机制

图片路径存储:使用列表self.images存储所有图片路径

索引管理self.current_index跟踪当前显示的图片索引

图片加载:通过QFileDialog选择多个图片文件

图片缩放:使用scaled()方法自适应调整图片大小

3. 轮播控制机制

定时器管理:使用QTimer实现定时切换

状态标记self.is_running跟踪轮播状态

按钮状态更新:根据图片加载情况和轮播状态启用 / 禁用按钮

4. 事件处理

定时器事件timer.timeout信号连接到next_image()

按钮点击事件:分别连接到对应的控制方法

窗口大小改变事件:重写resizeEvent()确保图片适应窗口变化

运行效果与交互流程

  1. 初始状态

    • 显示灰色背景的图片区域
    • 状态标签显示 "无图片"
    • 除 "加载图片" 外所有按钮禁用
  2. 加载图片后

    • 第一张图片显示在图片区域
    • 状态标签显示当前图片信息(如 "图片 1/5: image.jpg")
    • 所有按钮启用
  3. 手动切换图片

    • 点击 "上一张"/"下一张" 按钮切换图片
    • 状态标签更新显示新的图片信息
  4. 自动轮播

    • 点击 "开始轮播" 按钮,图片每 3 秒自动切换
    • 按钮文本变为 "停止轮播"
    • 状态标签显示 "轮播中"
  5. 停止轮播

    • 点击 "停止轮播" 按钮,暂停自动切换
    • 按钮文本变回 "开始轮播"
    • 状态标签显示 "轮播已暂停"

技术要点说明

  1. 定时器机制

    • QTimerstart(interval)方法启动定时器
    • timeout信号连接到处理函数(next_image()
    • stop()方法停止定时器
  2. 图片处理

    • QPixmap加载和处理图片
    • scaled()方法调整图片大小,保持宽高比
    • SmoothTransformation参数确保图片缩放质量
  3. 界面交互优化

    • 按钮状态管理:根据是否有图片和轮播状态动态启用 / 禁用
    • 状态标签:提供明确的用户反馈
    • 窗口大小自适应:重写resizeEvent()实现
  4. 文件对话框

    • QFileDialog.getOpenFileNames()选择多个文件
    • 文件类型过滤器:限制只能选择图片文件

扩展应用场景

  1. 产品展示:电商网站的商品图片轮播
  2. 幻灯片演示:创建简单的幻灯片播放功能
  3. 相册浏览:本地图片浏览应用
  4. 广告展示:网站或应用中的广告轮播
  5. 引导页:应用首次启动的功能介绍轮播

        这个完整的图片轮播组件示例,展示了如何使用定时器实现自动切换效果,通过合理的状态管理和用户交互设计,提供了一个功能完善、界面友好的图片轮播功能。

4.6.5  模拟网络请求延迟

场景:测试 UI 在网络请求时的加载状态
效果:点击按钮后显示加载状态,一段时间后显示结果

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout
from PyQt5.QtCore import QTimer, Qt  # 添加Qt导入
import sysclass NetworkDemo(QWidget):def __init__(self):super().__init__()# 设置窗口属性self.setWindowTitle("网络请求模拟")self.setGeometry(300, 300, 300, 200)# 创建按钮和结果标签self.button = QPushButton("发送请求", self)self.button.setMinimumHeight(40)  # 设置按钮高度self.result_label = QLabel("等待请求...", self)self.result_label.setAlignment(Qt.AlignCenter)  # 修正:使用Qt.AlignCenterself.result_label.setMinimumHeight(50)  # 设置标签高度self.result_label.setStyleSheet("font-size: 16px;")  # 设置字体大小# 创建进度标签(用于显示加载动画)self.progress_label = QLabel("", self)self.progress_label.setAlignment(Qt.AlignCenter)  # 修正:使用Qt.AlignCenterself.progress_label.setStyleSheet("color: #888; font-size: 12px;")# 布局设置layout = QVBoxLayout()layout.addStretch()  # 添加伸缩项,使内容居中layout.addWidget(self.result_label)layout.addWidget(self.progress_label)layout.addWidget(self.button)layout.addStretch()  # 添加伸缩项,使内容居中self.setLayout(layout)# 连接信号与槽self.button.clicked.connect(self.send_request)# 初始化定时器和状态变量self.timer_id = Noneself.loading_timer = None  # 加载动画定时器self.dots_count = 0  # 加载动画的点数量def send_request(self):"""模拟发送网络请求"""# 禁用按钮,防止重复点击self.button.setEnabled(False)self.result_label.setText("加载中...")self.result_label.setStyleSheet("font-size: 16px; color: #333;")# 启动加载动画定时器(每300ms更新一次)self.loading_timer = self.startTimer(300)self.dots_count = 0# 启动主定时器,模拟2秒网络延迟self.timer_id = self.startTimer(2000)def timerEvent(self, event):"""处理定时器事件"""# 区分不同定时器if event.timerId() == self.timer_id:# 主定时器触发(2秒后)self.handle_network_response()elif event.timerId() == self.loading_timer:# 加载动画定时器触发(每300ms)self.update_loading_animation()def update_loading_animation(self):"""更新加载动画"""self.dots_count = (self.dots_count + 1) % 4self.progress_label.setText("." * self.dots_count)def handle_network_response(self):"""处理网络响应"""# 停止所有定时器if self.timer_id:self.killTimer(self.timer_id)self.timer_id = Noneif self.loading_timer:self.killTimer(self.loading_timer)self.loading_timer = None# 更新UI显示结果self.result_label.setText("请求成功!")self.result_label.setStyleSheet("font-size: 16px; color: green;")self.progress_label.setText("")# 重新启用按钮self.button.setEnabled(True)# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = NetworkDemo()window.show()sys.exit(app.exec_())

 

代码功能模块解读

1. 界面组件与布局

主窗口:设置标题和尺寸

按钮:触发网络请求,带有禁用 / 启用状态变化

结果标签:显示请求状态(等待请求、加载中、请求成功)

进度标签:显示加载动画(动态的点)

垂直布局:使用伸缩项使内容垂直居中

2. 双定时器机制

主定时器:模拟 2 秒的网络延迟

加载动画定时器:每 300ms 更新一次加载动画(点的数量)

3. 状态管理

按钮状态:请求过程中禁用,完成后启用

文本显示:根据不同阶段更新标签文本

样式变化:成功时文本变为绿色

4. 事件处理

按钮点击事件:触发send_request()方法

定时器事件

  • timerEvent():根据定时器 ID 区分处理不同定时器
  • update_loading_animation():更新加载动画的点数量
  • handle_network_response():处理请求完成后的 UI 更新

运行效果与交互流程

  1. 初始状态

    • 按钮显示 "发送请求",可用状态
    • 结果标签显示 "等待请求..."
    • 进度标签为空
  2. 点击按钮后

    • 按钮变为禁用状态
    • 结果标签显示 "加载中..."
    • 进度标签开始显示动态的点(如 ".", "..", "..." 循环)
  3. 2 秒后

    • 结果标签变为 "请求成功!" 并变为绿色
    • 进度标签清空
    • 按钮重新启用

技术要点说明

  1. 多定时器管理

    • 使用不同的定时器 ID 区分不同功能
    • 通过timerId()方法判断是哪个定时器触发的事件
  2. 加载动画实现

    • 利用模运算(% 4)实现 0-3 个点的循环显示
    • 每 300ms 更新一次,创造平滑动画效果
  3. 界面优化

    • 使用伸缩项(addStretch())实现内容垂直居中
    • 通过样式表(setStyleSheet())设置字体大小和颜色
    • 设置控件最小高度增强视觉效果
  4. 状态安全性

    • 停止定时器前检查是否存在,避免错误
    • 禁用按钮防止重复请求,提升用户体验

扩展应用场景

  1. 真实网络请求:替换定时器逻辑,使用QNetworkAccessManager发送实际 HTTP 请求
  2. 多请求处理:扩展为可以处理多个并发请求的加载状态
  3. 请求失败处理:添加随机失败概率,显示不同的错误信息
  4. 进度条显示:使用QProgressBar替代点动画,显示更精确的加载进度
  5. 请求参数配置:添加输入框,允许用户设置请求 URL 和参数

        这个完整的网络请求模拟示例,展示了如何使用定时器模拟网络延迟和加载状态,通过合理的 UI 设计和状态管理,提供了一个直观且友好的网络请求交互体验。

4.6.6  键盘重复延迟控制

场景:游戏或需要精确键盘控制的应用
效果:长按按键时,初始延迟后持续触发

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt, QTimer
import sysclass GameWidget(QWidget):def __init__(self):super().__init__()# 设置窗口属性self.setWindowTitle("游戏按键控制演示")self.setGeometry(300, 300, 400, 300)self.setFocusPolicy(Qt.StrongFocus)  # 设置窗口可获取焦点# 角色属性self.player_x = 100  # 角色X坐标self.player_y = 150  # 角色Y坐标self.player_size = 50  # 角色大小# 按键状态self.key_timer = Noneself.current_key = Noneself.move_speed = 5  # 移动速度# 创建状态标签self.status_label = QLabel("按左右方向键移动角色", self)self.status_label.setAlignment(Qt.AlignCenter)# 布局设置layout = QVBoxLayout()layout.addWidget(self.status_label)self.setLayout(layout)def paintEvent(self, event):"""绘制游戏画面"""painter = QPainter(self)painter.setRenderHint(QPainter.Antialiasing)# 绘制背景painter.fillRect(self.rect(), QColor(240, 240, 240))# 绘制角色(红色圆形)painter.setBrush(QColor(255, 50, 50))painter.drawEllipse(self.player_x, self.player_y,self.player_size, self.player_size)def keyPressEvent(self, event):"""按键按下事件处理"""key = event.key()# 处理左右方向键if key == Qt.Key_Left or key == Qt.Key_Right:if not self.key_timer:self.current_key = key# 初始延迟500msself.key_timer = self.startTimer(500)self.update()  # 立即更新画面super().keyPressEvent(event)  # 调用父类处理其他按键def keyReleaseEvent(self, event):"""按键释放事件处理"""key = event.key()if self.key_timer and key == self.current_key:self.killTimer(self.key_timer)self.key_timer = Noneself.current_key = Noneself.status_label.setText("按左右方向键移动角色")self.update()  # 更新画面def timerEvent(self, event):"""定时器事件处理"""if not self.current_key:return# 初始延迟后,切换到更快的重复速率if self.key_timer and event.timerId() == self.key_timer:self.killTimer(self.key_timer)self.key_timer = self.startTimer(100)  # 100ms重复一次# 处理按键逻辑if self.current_key == Qt.Key_Left:self.player_x -= self.move_speedself.status_label.setText("向左移动...")elif self.current_key == Qt.Key_Right:self.player_x += self.move_speedself.status_label.setText("向右移动...")# 限制角色移动范围self.player_x = max(0, min(self.player_x, self.width() - self.player_size))self.update()  # 更新画面# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = GameWidget()window.show()sys.exit(app.exec_())

 

代码功能模块解读

1. 游戏界面与角色

窗口设置:设置标题、尺寸和焦点策略(确保能接收按键事件)

角色绘制:使用paintEvent绘制红色圆形角色

状态标签:显示当前操作状态和提示信息

2. 按键控制机制

初始延迟:首次按键后等待 500ms 才开始重复

重复速率:初始延迟后切换到 100ms 的快速重复

按键释放处理:释放按键时停止定时器,清除当前按键状态

3. 角色移动逻辑

左右方向键:控制角色在水平方向移动

边界检测:确保角色不会移出窗口边界

实时更新:每次移动后调用update()刷新画面

运行效果与交互流程

  1. 初始状态

    • 窗口中央显示红色圆形角色
    • 状态标签显示 "按左右方向键移动角色"
  2. 按下方向键

    • 角色开始向对应方向移动
    • 状态标签显示移动方向(如 "向左移动...")
    • 首次按键有 0.5 秒延迟,之后每 0.1 秒移动一次
  3. 释放方向键

    • 角色停止移动
    • 状态标签恢复初始提示
    • 定时器停止,等待下一次按键
  4. 连续按键效果

    • 长按方向键时,角色先有轻微停顿,然后快速连续移动
    • 模拟真实游戏中的按键重复体验

技术要点说明

  1. 按键事件处理

    • keyPressEvent:处理按键按下事件
    • keyReleaseEvent:处理按键释放事件
    • setFocusPolicy(Qt.StrongFocus):确保窗口能接收按键事件
  2. 定时器机制

    • 初始定时器:500ms 延迟,模拟人类反应时间
    • 重复定时器:100ms 间隔,提供流畅的连续移动体验
    • timerEvent中动态切换定时器,实现延迟后加速
  3. 图形绘制

    • paintEvent:重绘窗口内容
    • QPainter:绘制角色和背景
    • update():触发重绘事件
  4. 用户体验优化

    • 状态标签实时反馈当前操作
    • 边界检测防止角色移出屏幕
    • 符合游戏操作习惯的按键重复逻辑

扩展应用场景

  1. 平台游戏:控制角色在平台间跳跃移动
  2. 赛车游戏:方向键控制赛车转向
  3. 射击游戏:按键控制角色移动和射击
  4. 解谜游戏:方向键控制角色在迷宫中移动
  5. 2D 格斗游戏:组合按键触发不同技能

        这个完整的游戏按键控制示例,展示了如何使用定时器实现符合游戏操作习惯的按键重复逻辑,通过合理的延迟设置和状态管理,提供了流畅自然的游戏交互体验。

4.6.7 限时操作

场景:限时答题、验证码倒计时等有时间限制的操作
效果:显示剩余时间,时间到后自动处理

from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,QLabel, QPushButton, QMessageBox)
from PyQt5.QtCore import Qt, QTimer
import sysclass QuizWidget(QWidget):def __init__(self):super().__init__()# 设置窗口属性self.setWindowTitle("限时答题")self.setGeometry(300, 300, 500, 400)# 答题时间设置self.time_left = 30  # 30秒限时self.timer_id = None  # 定时器ID# 创建UI组件# 倒计时标签self.timer_label = QLabel(f"剩余时间: {self.time_left}秒", self)self.timer_label.setAlignment(Qt.AlignCenter)self.timer_label.setStyleSheet("font-size: 16px; color: #333;")# 题目标签self.question_label = QLabel("这是一道测试题目,请问Python的创始人是谁?", self)self.question_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)self.question_label.setWordWrap(True)  # 自动换行self.question_label.setStyleSheet("font-size: 14px; margin: 10px 0;")# 选项按钮self.options = []option_layout = QVBoxLayout()for i, text in enumerate(["A. 詹姆斯·高斯林", "B. 吉多·范罗苏姆", "C. 林纳斯·托瓦兹", "D. 比尔·盖茨"]):btn = QPushButton(text, self)btn.setMinimumHeight(40)btn.setStyleSheet("text-align: left; padding: 5px 10px;")btn.clicked.connect(lambda checked, idx=i: self.select_option(idx))self.options.append(btn)option_layout.addWidget(btn)# 提交按钮self.submit_btn = QPushButton("提交答案", self)self.submit_btn.setMinimumHeight(40)self.submit_btn.setStyleSheet("background-color: #4CAF50; color: white;")self.submit_btn.clicked.connect(self.submit_answer)self.submit_btn.setEnabled(False)  # 初始禁用,选择选项后启用# 结果标签self.result_label = QLabel("", self)self.result_label.setAlignment(Qt.AlignCenter)self.result_label.setStyleSheet("font-size: 16px; margin-top: 10px;")# 主布局main_layout = QVBoxLayout()main_layout.addWidget(self.timer_label)main_layout.addWidget(self.question_label)main_layout.addLayout(option_layout)main_layout.addWidget(self.submit_btn)main_layout.addWidget(self.result_label)main_layout.setSpacing(15)main_layout.setContentsMargins(20, 20, 20, 20)self.setLayout(main_layout)# 答题状态self.selected_option = Noneself.answered = False# 启动定时器self.timer_id = self.startTimer(1000)  # 每秒触发一次def timerEvent(self, event):"""定时器事件处理"""if event.timerId() == self.timer_id:self.time_left -= 1self.timer_label.setText(f"剩余时间: {self.time_left}秒")# 更新倒计时显示样式if self.time_left <= 10:self.timer_label.setStyleSheet("font-size: 16px; color: red; font-weight: bold;")if self.time_left <= 0:self.killTimer(self.timer_id)self.timer_label.setText("时间到!")self.handle_timeout()def select_option(self, index):"""选择选项"""if self.answered:return# 更新选中状态self.selected_option = index# 更新按钮样式for i, btn in enumerate(self.options):if i == index:btn.setStyleSheet("text-align: left; padding: 5px 10px; background-color: #ddd;")else:btn.setStyleSheet("text-align: left; padding: 5px 10px;")# 启用提交按钮self.submit_btn.setEnabled(True)def submit_answer(self):"""提交答案"""if self.answered or self.selected_option is None:returnself.answered = True# 停止定时器if self.timer_id:self.killTimer(self.timer_id)self.timer_id = None# 禁用所有选项按钮和提交按钮for btn in self.options:btn.setEnabled(False)self.submit_btn.setEnabled(False)# 检查答案是否正确(正确答案为B)is_correct = self.selected_option == 1# 显示结果if is_correct:self.result_label.setText("回答正确!🎉")self.result_label.setStyleSheet("font-size: 16px; margin-top: 10px; color: green;")else:self.result_label.setText(f"回答错误,正确答案是: B. 吉多·范罗苏姆")self.result_label.setStyleSheet("font-size: 16px; margin-top: 10px; color: red;")def handle_timeout(self):"""处理超时逻辑"""if not self.answered:self.answered = True# 禁用所有选项按钮和提交按钮for btn in self.options:btn.setEnabled(False)self.submit_btn.setEnabled(False)# 显示结果self.result_label.setText(f"时间到!正确答案是: B. 吉多·范罗苏姆")self.result_label.setStyleSheet("font-size: 16px; margin-top: 10px; color: red;")# 主程序入口
if __name__ == "__main__":app = QApplication(sys.argv)window = QuizWidget()window.show()sys.exit(app.exec_())

代码功能模块解读

1. 界面组件与布局

倒计时显示:顶部显示剩余时间,时间不足 10 秒时变红

题目展示:显示问题文本,支持自动换行

选项按钮:四个选项按钮,点击时改变样式

提交按钮:选择选项后启用,点击提交答案

结果显示:底部显示答题结果(正确 / 错误)

2. 计时机制

定时器控制:每秒触发一次,更新剩余时间

超时处理:时间到自动提交答案,显示正确答案

样式变化:时间不足时改变倒计时文本样式

3. 答题逻辑

选项选择:点击选项按钮记录选择,更新样式

答案提交:点击提交按钮或超时后检查答案

状态管理:使用answered标记防止重复提交

4. 交互优化

按钮状态控制:未选择选项时禁用提交按钮

视觉反馈:选中选项高亮显示

结果展示:清晰显示答题结果和正确答案

运行效果与交互流程

  1. 初始状态

    • 显示题目和四个选项
    • 倒计时显示 30 秒
    • 提交按钮禁用
  2. 选择选项

    • 点击选项按钮,按钮背景变灰
    • 提交按钮变为可用状态
  3. 提交答案

    • 点击提交按钮,停止计时
    • 禁用所有选项按钮
    • 显示答题结果(正确 / 错误)
  4. 超时处理

    • 倒计时结束时自动提交
    • 禁用所有选项按钮
    • 显示正确答案

技术要点说明

  1. 定时器机制

    • startTimer(1000):创建每秒触发的定时器
    • timerEvent():处理定时器触发事件
    • killTimer():停止定时器
  2. 事件处理

    • 按钮点击事件:使用 lambda 表达式传递选项索引
    • 选项选择逻辑:通过索引管理选中状态
  3. 样式控制

    • 使用setStyleSheet()设置按钮和标签样式
    • 根据答题状态动态更新样式
  4. 状态管理

    • answered标记:防止重复提交
    • selected_option:记录用户选择
    • 超时和手动提交的统一处理逻辑

扩展应用场景

  1. 在线考试系统:为考试题目添加限时功能
  2. 知识竞赛:实现抢答和限时答题功能
  3. 游戏问答环节:增加时间压力提升游戏紧张感
  4. 学习应用:限时测试帮助用户提高答题速度
  5. 心理测试:限时回答问题以获取更真实的反应

        这个完整的限时答题组件示例,展示了如何使用定时器实现倒计时功能,通过合理的状态管理和交互设计,提供了一个功能完善、界面友好的答题体验。

http://www.lryc.cn/news/572350.html

相关文章:

  • LVS 负载均衡群集
  • LeetCode | 二分法题型详解+图解
  • bos_token; eos_token; pad_token是什么
  • QSqlDatabase: QSQLITE driver not loaded
  • infinisynapse 使用清华源有问题的暂时解决方法:换回阿里云源并安装配置PPA
  • LoRA 浅析
  • Python Beautiful Soup 4【HTML/XML解析库】 简介
  • StableDiffusion实战-手机壁纸制作 第一篇:从零基础到生成艺术品的第一步!
  • Hexo 个人博客配置记录(GitHub Pages + Butterfly 主题 + Waline 评论 + 自动部署)
  • Kernel K-means:让K-means在非线性空间“大显身手”
  • 职坐标IT培训:嵌入式AI物联网开源项目精选
  • 基于大模型的急性结石性胆囊炎全流程预测与诊疗方案研究
  • 【图像处理入门】11. 深度学习初探:从CNN到GAN的视觉智能之旅
  • 跟着AI学习C# Day22
  • 实时输出subprocess.Popen运行程序的日志
  • 永磁同步电机无速度算法--基于正切函数锁相环的滑模观测器
  • 【鸿蒙HarmonyOS Next App实战开发】​​​​ArkUI纯色图生成器
  • VACM 详解:SNMPv3 的访问控制核心
  • 回溯----8.N皇后
  • C++ std::set的用法
  • 根据图片理解maven
  • FocalAD论文阅读
  • SpringBoot 应用开发核心分层架构与实战详解
  • SpringBoot电脑商城项目--修改默认收货地址
  • 计算机网络:(四)物理层的基本概念,数据通信的基础知识,物理层下面的传输媒体
  • Mac电脑-Office 2024 长期支持版(Excel、Word、PPT)
  • 【数据破茧成蝶】企业数据标准:AI时代的智能罗盘与增长基石
  • 探索大语言模型(LLM):Lora vs. QLora:参数高效微调的双生花,你该选谁?
  • 协作式机器人助力提高生产速度和效益
  • Java泛型详解与阿里FastJSON源码中的巧妙运用