Qt Designer与事件处理:构建优雅的GUI应用
一、Qt Designer
Qt Designer是Qt框架提供的可视化界面设计工具,让开发者能够通过拖放组件的方式快速构建用户界面,大大提高了GUI应用的开发效率。
1.1 基本使用
1.1.1 设计界面
使用Qt Designer设计界面非常简单直观:
- 打开Qt Designer,选择适合的窗口模板(如Main Window、Dialog等)
- 从左侧组件面板拖放需要的控件到窗体上
- 使用属性编辑器调整控件的属性
- 通过右键菜单或布局工具栏对控件进行布局管理
- 保存设计文件,生成.ui文件
设计完成后,可以通过pyuic5工具将.ui文件转换为Python代码:
bash
pyuic5 design.ui -o design.py
1.1.2 如何使用ui上面的控件
在代码中使用设计的界面有两种主要方式:
方法一:直接使用生成的Python代码
python
from PyQt5 import QtWidgets
from design import Ui_MainWindow
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# 访问界面上的控件
self.ui.pushButton.clicked.connect(self.on_button_click)
def on_button_click(self):
self.ui.label.setText("按钮被点击了!")
app = QtWidgets.QApplication([])
window = MyWindow()
window.show()
app.exec_()
方法二:动态加载.ui文件
python
from PyQt5 import QtWidgets, uic
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('design.ui', self)
# 直接访问控件
self.pushButton.clicked.connect(self.on_button_click)
def on_button_click(self):
self.label.setText("按钮被点击了!")
app = QtWidgets.QApplication([])
window = MyWindow()
window.show()
app.exec_()
1.2 常见布局
良好的布局管理是创建响应式界面的关键。
1.2.1 水平布局
水平布局(QHBoxLayout)将控件从左到右排列在同一行中。
python
# 创建水平布局示例
h_layout = QHBoxLayout()
h_layout.addWidget(QPushButton("按钮1"))
h_layout.addWidget(QPushButton("按钮2"))
h_layout.addWidget(QPushButton("按钮3"))
1.2.2 垂直布局
垂直布局(QVBoxLayout)将控件从上到下排列在同一列中。
python
# 创建垂直布局示例
v_layout = QVBoxLayout()
v_layout.addWidget(QPushButton("按钮1"))
v_layout.addWidget(QPushButton("按钮2"))
v_layout.addWidget(QPushButton("按钮3"))
1.2.3 网格布局
网格布局(QGridLayout)将控件排列在网格中,可以精确控制每个控件的位置和大小。
python
# 创建网格布局示例
grid_layout = QGridLayout()
grid_layout.addWidget(QPushButton("(0,0)"), 0, 0)
grid_layout.addWidget(QPushButton("(0,1)"), 0, 1)
grid_layout.addWidget(QPushButton("(1,0)"), 1, 0)
grid_layout.addWidget(QPushButton("(1,1)"), 1, 1)
1.2.4 借助弹簧布局
弹簧(QSpacerItem)可以帮助在布局中创建弹性空间,使界面在不同窗口大小下保持美观。
python
# 使用弹簧的布局示例
h_layout = QHBoxLayout()
h_layout.addWidget(QPushButton("左按钮"))
h_layout.addStretch(1) # 添加弹簧
h_layout.addWidget(QPushButton("右按钮"))
1.2.5 嵌套布局
复杂的界面通常需要嵌套使用多种布局方式。
python
# 嵌套布局示例
main_layout = QVBoxLayout()
top_layout = QHBoxLayout()
bottom_layout = QGridLayout()
top_layout.addWidget(QPushButton("顶部按钮1"))
top_layout.addWidget(QPushButton("顶部按钮2"))
bottom_layout.addWidget(QPushButton("底部按钮1"), 0, 0)
bottom_layout.addWidget(QPushButton("底部按钮2"), 0, 1)
main_layout.addLayout(top_layout)
main_layout.addLayout(bottom_layout)
二、事件处理
事件处理是GUI编程的核心,Qt提供了强大且灵活的事件系统。
2.1 鼠标事件QMouseEvent
鼠标事件处理允许应用程序响应用户的鼠标操作。
python
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout
from PyQt5.QtGui import QMouseEvent
class MouseEventDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('鼠标事件示例')
self.label = QLabel('请在这里点击或移动鼠标')
layout = QVBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
def mousePressEvent(self, event: QMouseEvent):
button = ""
if event.button() == Qt.LeftButton:
button = "左键"
elif event.button() == Qt.RightButton:
button = "右键"
elif event.button() == Qt.MiddleButton:
button = "中键"
self.label.setText(f"鼠标按下: {button} at ({event.x()}, {event.y()})")
def mouseMoveEvent(self, event: QMouseEvent):
self.label.setText(f"鼠标移动: ({event.x()}, {event.y()})")
def mouseReleaseEvent(self, event: QMouseEvent):
self.label.setText(f"鼠标释放: ({event.x()}, {event.y()})")
def mouseDoubleClickEvent(self, event: QMouseEvent):
self.label.setText(f"鼠标双击: ({event.x()}, {event.y()})")
2.2 绘图事件QPaintEvent
绘图事件允许在控件上自定义绘制图形、文本和图像。
2.4.1 绘图
使用QPainter进行自定义绘图:
python
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QPen, QBrush
from PyQt5.QtCore import Qt
import sys
class DrawingDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('绘图示例')
self.setGeometry(100, 100, 600, 400)
def paintEvent(self, event):
painter = QPainter(self)
# 设置抗锯齿渲染
painter.setRenderHint(QPainter.Antialiasing)
# 绘制矩形
painter.setPen(QPen(Qt.blue, 3))
painter.setBrush(QBrush(Qt.lightGray))
painter.drawRect(50, 50, 200, 100)
# 绘制椭圆
painter.setPen(QPen(Qt.red, 2))
painter.setBrush(QBrush(Qt.yellow))
painter.drawEllipse(300, 50, 200, 100)
# 绘制文本
painter.setPen(QPen(Qt.black))
painter.setFont(self.font())
painter.drawText(50, 200, "这是使用QPainter绘制的文本")
# 绘制直线
painter.setPen(QPen(Qt.green, 4))
painter.drawLine(50, 250, 500, 250)
# 绘制多边形
painter.setPen(QPen(Qt.darkMagenta, 2))
painter.setBrush(QBrush(Qt.cyan))
points = [
(100, 300), (150, 350), (200, 300),
(250, 350), (300, 300), (350, 350)
]
for i in range(len(points) - 1):
painter.drawLine(points[i][0], points[i][1],
points[i+1][0], points[i+1][1])
2.4.2 刷新绘图区域
当需要更新绘图内容时,可以调用以下方法刷新特定区域:
python
class RefreshDemo(QWidget):
def __init__(self):
super().__init__()
self.angle = 0
self.timer = QTimer()
self.timer.timeout.connect(self.update_rotation)
self.timer.start(100) # 每100毫秒更新一次
def update_rotation(self):
self.angle = (self.angle + 5) % 360
# 更新整个窗口
self.update()
# 或者只更新特定区域
# self.update(x, y, width, height)
def paintEvent(self, event):
painter = QPainter(self)
painter.translate(self.width() / 2, self.height() / 2)
painter.rotate(self.angle)
painter.drawRect(-50, -50, 100, 100)
三、自定义信号
Qt的信号槽机制是其核心特性之一,允许对象之间进行低耦合的通信。
python
from PyQt5.QtCore import QObject, pyqtSignal, QTimer
from PyQt5.QtWidgets import QApplication, QLabel
# 创建自定义信号
class Worker(QObject):
# 定义信号
progressUpdated = pyqtSignal(int) # 进度更新信号
workFinished = pyqtSignal(str) # 工作完成信号
def do_work(self):
for i in range(101):
# 模拟工作进度
QTimer.singleShot(i * 100, lambda i=i: self.progressUpdated.emit(i))
# 工作完成
self.workFinished.emit("工作已完成!")
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("准备开始工作...", self)
self.label.setGeometry(10, 10, 200, 30)
self.worker = Worker()
# 连接信号到槽函数
self.worker.progressUpdated.connect(self.update_progress)
self.worker.workFinished.connect(self.on_work_finished)
# 开始工作
self.worker.do_work()
def update_progress(self, value):
self.label.setText(f"工作进度: {value}%")
def on_work_finished(self, message):
self.label.setText(message)
# 使用lambda表达式连接信号
button.clicked.connect(lambda: self.on_button_clicked("参数"))
# 使用functools.partial连接信号
from functools import partial
button.clicked.connect(partial(self.on_button_clicked, "参数"))
四、图片资源
在Qt应用中管理图片资源有多种方式:
使用Qt资源系统(.qrc文件)
- 创建.qrc资源文件:
xml
<RCC>
<qresource prefix="/images">
<file>icons/app_icon.png</file>
<file>icons/save_icon.png</file>
<file>images/background.jpg</file>
</qresource>
</RCC>
- 使用pyrcc5编译资源文件:
bash
pyrcc5 resources.qrc -o resources.py
- 在代码中使用资源:
python
# 导入编译后的资源模块
import resources
# 使用资源
icon = QIcon(":/images/icons/app_icon.png")
background = QPixmap(":/images/background.jpg")
# 在样式表中使用资源
self.setStyleSheet("""
QMainWindow {
background-image: url(:/images/background.jpg);
}
QPushButton {
icon: url(:/images/icons/save_icon.png);
}
""")
动态加载图片文件
python
# 从文件加载图片
pixmap = QPixmap("path/to/image.png")
label.setPixmap(pixmap)
# 缩放图片
scaled_pixmap = pixmap.scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# 绘制图片
painter = QPainter(self)
painter.drawPixmap(0, 0, pixmap)
使用QPixmapCache优化性能
对于需要频繁使用的小图片,可以使用QPixmapCache提高性能:
python
from PyQt5.QtGui import QPixmapCache
# 设置缓存大小(KB)
QPixmapCache.setCacheLimit(2048) # 2MB
# 缓存图片
pixmap = QPixmap("icon.png")
QPixmapCache.insert("my_icon", pixmap)
# 获取缓存的图片
cached_pixmap = QPixmapCache.find("my_icon")
if cached_pixmap:
label.setPixmap(cached_pixmap)