5、使用Qt Designer模拟打印的界面
实现功能:在打印时可以设置打印的份数、纸张类型,触发“打印”按钮后,将执行结果显示在右侧;通过QCheckBox(“全屏预览”复选框)来选择是否通过全屏模式进行预览,将执行结果显示在右侧。按F1键,可以显示helpMessage信息。
实现步骤:(1)使用Qt Designer新建一个模板名为“Widget”的简单窗口,该窗口文件名为MainWinSignalSlog02.ui。通过将Widget Box区域的控件拖拽到窗口中,实现下图的界面效果:
(2)将界面文件转换为Python文件。在eric6里运行对应的MainWinSignalSlog02.py,结果如下:
虽然这时界面已经做出来了,但是信号和槽没有连接,所以点击图中的button都没有任何反应。
(3)为了使窗口的显示和业务逻辑分离,再新建一个调用窗口显示的文件CallMainWinSignalSlog02.py,在调用类中添加多个自定义信号,并与槽函数进行绑定。然后运行该脚本,结果如下:
注意在运行CallMainWinSignalSlog02时引入的界面文件名字要与自己起的那个名字相同并且处于同一个package里。
6、多线程中信号与槽的使用
最简单的多线程使用方法是利用QThread函数,如下代码展示了QThread函数和信号与槽简单的结合方法。
""" | |
【简介】 | |
多线程信号槽通信示例 | |
""" | |
from PyQt5.QtWidgets import QApplication ,QWidget | |
from PyQt5.QtCore import QThread , pyqtSignal | |
import sys | |
class Main(QWidget): | |
def __init__(self, parent = None): | |
super(Main,self).__init__(parent) | |
# 创建一个线程实例并设置名称、变量、信号槽 | |
self.thread = MyThread() | |
self.thread.setIdentity("thread1") | |
self.thread.sinOut.connect(self.outText) | |
self.thread.setVal(6) | |
def outText(self,text): | |
print(text) | |
class MyThread(QThread): | |
sinOut = pyqtSignal(str) | |
def __init__(self,parent=None): | |
super(MyThread,self).__init__(parent) | |
self.identity = None | |
def setIdentity(self,text): | |
self.identity = text | |
def setVal(self,val): | |
self.times = int(val) | |
#执行线程的run方法 | |
self.start() | |
def run(self): | |
while self.times > 0 and self.identity: | |
# 发射信号 | |
self.sinOut.emit(self.identity+"==>"+str(self.times)) | |
self.times -= 1 | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
main = Main() | |
main.show() | |
sys.exit(app.exec_()) |
运行结果:
有时在开发程序时经常会执行一些耗时操作,这样就会导致界面卡顿,为了解决这个问题,就可以使用多线程,使用主线程更新界面,使用子线程实时处理数据,最后将结果显示到界面上。
如下的例子是定义了一个后台线程类BackendThread来模拟后台耗时操作,在这个线程类中定义了信号update_date。使用BackendThread线程类在后台处理数据,每秒发射依次自定义信号update_date。在初始化窗口界面时,定义后台线程类BackendThread,并把线程类的信号update_date连接到槽函数handleDisply()。这样后台线程每发射一次信号,就可以把最新的时间值实时显示在前台窗口的QLineEdit文本对话框中。完整代码如下:
""" | |
【简介】 | |
多线程更新跟新数据,pyqt5界面实时刷新例子 | |
""" | |
from PyQt5.QtCore import QThread , pyqtSignal, QDateTime | |
from PyQt5.QtWidgets import QApplication, QDialog, QLineEdit | |
import time | |
import sys | |
class BackendThread(QThread): | |
# 通过类成员对象定义信号对象 | |
update_date = pyqtSignal(str) | |
# 处理要做的业务逻辑 | |
def run(self): | |
while True: | |
data = QDateTime.currentDateTime() | |
currTime = data.toString("yyyy-MM-dd hh:mm:ss") | |
self.update_date.emit( str(currTime) ) | |
time.sleep(1) | |
class Window(QDialog): | |
def __init__(self): | |
QDialog.__init__(self) | |
self.setWindowTitle('pyqt5界面实时更新例子') | |
self.resize(400, 100) | |
self.input = QLineEdit(self) | |
self.input.resize(400, 100) | |
self.initUI() | |
def initUI(self): | |
# 创建线程 | |
self.backend = BackendThread() | |
# 连接信号 | |
self.backend.update_date.connect(self.handleDisplay) | |
# 开始线程 | |
self.backend.start() | |
#将当前时间输出到文本框 | |
def handleDisplay(self, data): | |
self.input.setText(data) | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
win = Window() | |
win.show() | |
sys.exit(app.exec_()) |
结果如下:
四、事件处理机制入门
PyQt为事件处理提供了两种机制:高级的信号与槽机制,以及低级的事件处理机制。其中,信号与槽只能解决窗口控件的某些特定行为,如果要对窗口控件做更深层次的研究,如自定义窗口等,则需要使用低级的事件处理机制,一般是在采用信号与槽机制处理不了问题的时候才会考虑使用事件处理机制。
1、事件和信号与槽的区别
信号与槽可以说是对事件处理机制的高级封装,如果说事件是用来创建窗口控件的,那么信号与槽就是用来对这个窗口控件进行使用的。
2、常见事件类型
键盘事件:按键按下和松开
鼠标事件:鼠标指针移动、鼠标按键按下和松开
拖放事件:用鼠标进行拖放
滚轮事件:鼠标滚轮滚动
绘屏事件:重绘屏幕的某些部分
定时事件:定时器到时
焦点事件:键盘焦点移动
进入和离开事件:鼠标指针移入Widget内,或者移出
移动事件:Widget的位置改变
大小改变事件:Widget的大小改变
显示和隐藏事件:Widget显示和隐藏
窗口事件:窗口是否为当前窗口
3、使用事件处理的方法
PyQt提供了如下5种事件处理和过滤方法(由弱到强),其中前两种方法使用最频繁
(1)重新实现事件函数
比如mousePressEvent()、keyPressEvent()、paintEvent()。这是最常规的事件处理方法
(2)重新实现QObject.event()
一般用在PyQt没有提供该事件的处理函数的情况下,即增加新事件时。
(3)安装事件过滤器
(4)在QApplication中安装事件过滤器
(5)重新实现QApplication的notify()方法
4、经典案例
见chapter07对应代码,此处略
五、窗口数据传递
1、单一窗口数据传递
对于具有单一窗口的程序来说,一个控件的变化会影响另一个控件的变化,这种变化利用信号与槽机制非常容易解决。例子如下:
""" | |
【简介】 | |
信号槽连接滑块LCD示例 | |
""" | |
import sys | |
from PyQt5.QtWidgets import QWidget,QLCDNumber,QSlider,QVBoxLayout,QApplication | |
from PyQt5.QtCore import Qt | |
class WinForm(QWidget): | |
def __init__(self): | |
super().__init__() | |
self.initUI() | |
def initUI(self): | |
#1 先创建滑块和 LCD 部件 | |
lcd = QLCDNumber(self) | |
slider = QSlider(Qt.Horizontal, self) | |
#2 通过QVboxLayout来设置布局 | |
vBox = QVBoxLayout() | |
vBox.addWidget(lcd) | |
vBox.addWidget(slider) | |
self.setLayout(vBox) | |
#3 valueChanged()是Qslider的一个信号函数,只要slider的值发生改变,它就会发射一个信号,然后通过connect连接信号的接收部件,也就是lcd。 | |
slider.valueChanged.connect(lcd.display) | |
self.setGeometry(300,300,350,150) | |
self.setWindowTitle("信号与槽:连接滑块LCD") | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
form = WinForm() | |
form.show() | |
sys.exit(app.exec_()) |
运行结果:
2、多窗口数据传递:调用属性
PyQt提供了一些标准的对话框类,用于输入数据、修改数据、更改应用的设置等,常见的有QFileDialog、QInputDialog、QColorDialog等。在不同的窗口之间传参有两种常用的方式:一种是在自定义对话框之间通过属性传参;另一种是在窗口之间使用信号与槽机制传参。
下面的代码是使用第一种方式传参,在该例中,将自定义对话框作为一个子窗口,后面会新建一个主窗口来调用这个子窗口的属性。
''' | |
【简介】 | |
对话框关闭时返回值给主窗口 例子 | |
''' | |
from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import * | |
class DateDialog(QDialog): | |
def __init__(self, parent=None): | |
super(DateDialog, self).__init__(parent) | |
self.setWindowTitle('DateDialog') | |
# 在布局中添加部件 | |
layout = QVBoxLayout(self) | |
self.datetime = QDateTimeEdit(self) | |
self.datetime.setCalendarPopup(True) | |
self.datetime.setDateTime(QDateTime.currentDateTime()) | |
layout.addWidget(self.datetime) | |
# 使用两个button(ok和cancel)分别连接accept()和reject()槽函数 | |
buttons = QDialogButtonBox( | |
QDialogButtonBox.Ok | QDialogButtonBox.Cancel, | |
Qt.Horizontal, self) | |
buttons.accepted.connect(self.accept) | |
buttons.rejected.connect(self.reject) | |
layout.addWidget(buttons) | |
# 从对话框中获取当前日期和时间 | |
def dateTime(self): | |
return self.datetime.dateTime() | |
# 静态方法创建对话框并返回 (date, time, accepted) | |
@staticmethod | |
def getDateTime(parent=None): | |
dialog = DateDialog(parent) | |
result = dialog.exec_() | |
date = dialog.dateTime() | |
return (date.date(), date.time(), result == QDialog.Accepted) |
新建一个调用对话框的主窗口文件
''' | |
【简介】 | |
对话框关闭时返回值给主窗口例子 | |
''' | |
import sys | |
from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import * | |
from DateDialog import DateDialog | |
class WinForm(QWidget): | |
def __init__(self, parent=None): | |
super(WinForm, self).__init__(parent) | |
self.resize(400, 90) | |
self.setWindowTitle('对话框关闭时返回值给主窗口例子') | |
self.lineEdit = QLineEdit(self) | |
self.button1 = QPushButton('弹出对话框1') | |
self.button1.clicked.connect(self.onButton1Click) | |
self.button2 = QPushButton('弹出对话框2') | |
self.button2.clicked.connect(self.onButton2Click) | |
gridLayout = QGridLayout() | |
gridLayout.addWidget(self.lineEdit) | |
gridLayout.addWidget(self.button1) | |
gridLayout.addWidget(self.button2) | |
self.setLayout(gridLayout) | |
def onButton1Click(self): | |
dialog = DateDialog(self) | |
result = dialog.exec_() | |
date = dialog.dateTime() | |
self.lineEdit.setText(date.date().toString()) | |
print('\n日期对话框的返回值') | |
print('date=%s' % str(date.date())) | |
print('time=%s' % str(date.time())) | |
print('result=%s' % result) | |
dialog.destroy() | |
def onButton2Click(self): | |
date, time, result = DateDialog.getDateTime() | |
self.lineEdit.setText(date.toString()) | |
print('\n日期对话框的返回值') | |
print('date=%s' % str(date)) | |
print('time=%s' % str(time)) | |
print('result=%s' % result) | |
if result == QDialog.Accepted: | |
print('点击确认按钮') | |
else: | |
print('点击取消按钮') | |
if __name__ == "__main__": | |
app = QApplication(sys.argv) | |
form = WinForm() | |
form.show() | |
sys.exit(app.exec_()) |
运行该脚本,结果为:
3、多窗口数据传递:信号与槽
对于多窗口的数据传递,一般是通过子窗口发射信号的,主窗口通过槽函数捕获这个信号,然后获取信号里面的数据。子窗口发射的信号有两种,其中一种是发射PyQt内置的一些信号;另一种是发射自定义的信号。发射自定义信号的好处是它的参数类型可以自定义。具体代码如下:
首先,建立一个对话框文件DateDialog2.py
from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import * | |
class DateDialog(QDialog): | |
Signal_OneParameter = pyqtSignal(str) | |
def __init__(self, parent=None): | |
super(DateDialog, self).__init__(parent) | |
self.setWindowTitle('子窗口:用来发射信号') | |
# 在布局中添加部件 | |
layout = QVBoxLayout(self) | |
self.label = QLabel(self) | |
self.label.setText('前者发射内置信号\n后者发射自定义信号') | |
self.datetime_inner = QDateTimeEdit(self) | |
self.datetime_inner.setCalendarPopup(True) | |
self.datetime_inner.setDateTime(QDateTime.currentDateTime()) | |
self.datetime_emit = QDateTimeEdit(self) | |
self.datetime_emit.setCalendarPopup(True) | |
self.datetime_emit.setDateTime(QDateTime.currentDateTime()) | |
layout.addWidget(self.label) | |
layout.addWidget(self.datetime_inner) | |
layout.addWidget(self.datetime_emit) | |
# 使用两个button(ok和cancel)分别连接accept()和reject()槽函数 | |
buttons = QDialogButtonBox( | |
QDialogButtonBox.Ok | QDialogButtonBox.Cancel, | |
Qt.Horizontal, self) | |
buttons.accepted.connect(self.accept) | |
buttons.rejected.connect(self.reject) | |
layout.addWidget(buttons) | |
self.datetime_emit.dateTimeChanged.connect(self.emit_signal) | |
def emit_signal(self): | |
date_str = self.datetime_emit.dateTime().toString() | |
self.Signal_OneParameter.emit(date_str) |
然后新建一个调用对话框的主窗口文件CallDialogMainWin2.py
''' | |
【简介】 | |
对话框关闭时返回值给主窗口例子 | |
''' | |
import sys | |
from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import * | |
from DateDialog2 import DateDialog | |
class WinForm(QWidget): | |
def __init__(self, parent=None): | |
super(WinForm, self).__init__(parent) | |
self.resize(400, 90) | |
self.setWindowTitle('信号与槽传递参数的示例') | |
self.open_btn = QPushButton('获取时间') | |
self.lineEdit_inner = QLineEdit(self) | |
self.lineEdit_emit = QLineEdit(self) | |
self.open_btn.clicked.connect(self.openDialog) | |
self.lineEdit_inner.setText('接收子窗口内置信号的时间') | |
self.lineEdit_emit.setText('接收子窗口自定义信号的时间') | |
grid = QGridLayout() | |
grid.addWidget(self.lineEdit_inner) | |
grid.addWidget(self.lineEdit_emit) | |
grid.addWidget(self.open_btn) | |
self.setLayout(grid) | |
def openDialog(self): | |
dialog = DateDialog(self) | |
'''连接子窗口的内置信号与主窗口的槽函数''' | |
dialog.datetime_inner.dateTimeChanged.connect(self.deal_inner_slot) | |
'''连接子窗口的自定义信号与主窗口的槽函数''' | |
dialog.Signal_OneParameter.connect(self.deal_emit_slot) | |
dialog.show() | |
def deal_inner_slot(self, date): | |
self.lineEdit_inner.setText(date.toString()) | |
def deal_emit_slot(self, dateStr): | |
self.lineEdit_emit.setText(dateStr) | |
if __name__ == "__main__": | |
app = QApplication(sys.argv) | |
form = WinForm() | |
form.show() | |
sys.exit(app.exec_()) |
运行结果:
至此,PyQt5信号与槽部分内容学习完毕。