往期文章:
一. 事件模型
事件模型由以下三个参与者:
- 事件源
- 事件对象(即事件)
- 事件接收者
其中,事件源是状态发生变化的对象,它将事件源中状态的变动封装,封装后的状态称为事件。事件源将事件发送给事件接收者,而后者负责处理事件。
在GUI程序中,常见的事件包括由用户触发的鼠标点击、文本输入等,也包括由其它程序触发的例如网络连接、window manger、定时器等。我们对QApplication实例化后对其使用exec_方法即可使程序进入主循环,并获取、分发事件。
二. 信号槽Signal&slots机制
PyQt5通过Signal&slots机制来实现事件模型。在这个机制中,事件源sender发送出事件signal,而对应的接收者receiver使用槽函数slots处理信号。
我们可以通过下面的例子清楚的看到这一机制的工作过程与框架:
import sys
from PyQt5.QtWidgets import QApplication,\
QWidget,\
QVBoxLayout,\
QLCDNumber,\
QSlider
from PyQt5.QtCore import Qt
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.setLCDandSlider()
self.setMyWindow()
def setLCDandSlider(self):
sender = QSlider(Qt.Horizontal,self)
signal = sender.valueChanged
receiver = QLCDNumber(self)
slits = receiver.display
signal.connect(slits)
vbox = QVBoxLayout()
vbox.addWidget(receiver)
vbox.addWidget(sender)
self.setLayout(vbox)
def setMyWindow(self):
self.resize(200,200)
self.move(0,0)
self.setWindowTitle('用滑动轴控制的数字显示窗')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
sys.exit(app.exec_())
其效果如图:
我们可以通过拖动滑条来改变显示的数字:
下面我们来具体分析这段代码。首先,我们调入一个QLCDNumber函数,这个函数的作用是将数字以液晶显示器的格式显示在窗口,例如:
def setMyLCDNumber(self):
lcd = QLCDNumber(self)
lcd.display(3.14)
lcd.resize(200,200)
这段代码制定了一个LCD显示窗口,并通过槽函数display指定了显示的数字,效果如下:
而QSlider函数则是在窗口中添加一个滑动轴。
在这段代码中,我们的时间源是滑动轴,而滑动轴发送的事件就是滑动轴的值sender.valueChanged;事件接收者是LCD显示窗,而其提供的处理事件的槽函数就是receiver.display。最后,我们通过signal.connect(slits)
将事件与槽函数联系在一起即可。
我们使用了纵向布局,所以申请了QVBoxLayout,而滑动轴默认是纵向的,为了美观我们向QSlider函数中传入Qt.Horizontal,将其调整为横向。若不指定该参数,最后的窗口效果如下:
三. 示例:使用signal&slots机制获取用户按钮动作信息
import sys
from PyQt5.QtWidgets import QApplication,\
QWidget,\
QMainWindow,\
QHBoxLayout,\
QVBoxLayout,\
QPushButton
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMyButton()
self.setMyWindow()
def setMyButton(self):
button_a = QPushButton('按钮a',self)
button_b = QPushButton('按钮b',self)
self.setButtonLayout(button_a,button_b)
myStatusBar = self.statusBar()
button_a.clicked.connect(self.MySlot)
button_b.clicked.connect(self.MySlot)
def setButtonLayout(self,button_a,button_b):
widget = QWidget()
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(button_a)
hbox.addStretch(1)
hbox.addWidget(button_b)
hbox.addStretch(1)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
vbox.addStretch(1)
widget.setLayout(vbox)
self.setCentralWidget(widget)
#将QWidget设置为QMainWindow的主窗口
def MySlot(self):
sender = self.sender()
self.statusBar().showMessage('刚刚触发的按钮是' + sender.text())
def setMyWindow(self):
self.resize(600,600)
self.move(0,0)
self.setWindowTitle('显示按钮信息')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
sys.exit(app.exec_())
在这段代码中,我们在窗口上放置了两个按钮,希望能把用户按按钮的信息显示在窗口底部的状态栏中,并且窗口使用了框布局,效果如下:
当我们点击按钮b时,下方出现提示:
下面我们来具体分析一下这段代码:
首先我们为了使信息显示在状态栏里,所以调入了QMainWindow类。QMainWindow类是QWidget类的子类,提供了状态栏等控件,我们的MyWindow类继承了QMainWindow。
之后,我们申请了两个按钮和一个状体栏,并使用自己写的方法对按钮的布局进行设置:
button_a = QPushButton('按钮a',self)
button_b = QPushButton('按钮b',self)
self.setButtonLayout(button_a,button_b)
myStatusBar = self.statusBar()
在setButtonLayout
方法中,我们使用了框布局,但是QMainWindow并未直接提供框布局方法,所以在setButtonLayout方法中,我们为了方便的使用框布局而增加了几行代码:
widget = QWidget()
widget.setLayout(vbox)
self.setCentralWidget(widget)
这几行代码使我们的主窗口仍然是QWidget类,既可以方便的使用QWidget类提供的布局方法,也能使用QMainWindow类提供的对状态栏的设置。
解决了布局问题之后,我们设置信息&槽;其中信息是按钮的点击,而槽函数是我们另外定义的:
button_a.clicked.connect(self.MySlot)
button_b.clicked.connect(self.MySlot)
两个信息或者说事件都指向同一个槽函数:
def MySlot(self):
sender = self.sender()
self.statusBar().showMessage('刚刚触发的按钮是' + sender.text())
这个函数使用了sender函数;这个函数的作用是获得用户点击窗口中某个控件的信息,我们使用text方法获得控件的信息显示文本并通过showMessage方法显示在了状态栏中。
这个示例中我们需要注意的是如何在继承自QMainWindow的类中使用QWidget类的方法;同时在定义槽函数时使用的sender方法也是我们需要关注的。
四. 实例:做选择
import sys
from PyQt5.QtWidgets import QApplication,\
QWidget,\
QMainWindow,\
QPushButton,\
QHBoxLayout,\
QVBoxLayout,\
QLabel
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMyWidget()
self.setMyWindow()
def setMyWidget(self):
'对窗口中的按钮控件进行设置'
label_tip = self.setMyLabel()
button_a,button_b = self.setMyButton()
self.setMyLayout(label_tip,button_a,button_b)
self.statusBar()
self.setMyAnswer(button_a,button_b)
def setMyWindow(self):
'设置窗口的基础内容'
self.resize(600,600)
self.move(0,0)
self.setWindowTitle('请做出选择')
self.show()
def setMyLabel(self):
label_tip = QLabel('如果有机会,你要选择金钱还是选择爱情?')
return label_tip
def setMyButton(self):
button_a = QPushButton('金钱')
button_b = QPushButton('爱情')
return button_a,button_b
def setMyLayout(self,label_tip,button_a,button_b):
widget = QWidget()
hbox_a = QHBoxLayout()
hbox_a.addStretch(1)
hbox_a.addWidget(label_tip)
hbox_a.addStretch(1)
hbox_b = QHBoxLayout()
hbox_b.addStretch(1)
hbox_b.addWidget(button_a)
hbox_b.addStretch(1)
hbox_b.addWidget(button_b)
hbox_b.addStretch(1)
vbox = QVBoxLayout()
vbox.addStretch(2)
vbox.addLayout(hbox_a)
vbox.addStretch(1)
vbox.addLayout(hbox_b)
vbox.addStretch(2)
widget.setLayout(vbox)
self.setCentralWidget(widget)
def setMyAnswer(self,button_a,button_b):
button_a.clicked.connect(self.slot_a)
button_b.clicked.connect(self.slot_b)
def slot_a(self):
self.statusBar().showMessage('你选择了金钱而不是爱情,生活很空虚。')
def slot_b(self):
self.statusBar().showMessage('你选择了爱情而不是金钱,生活很艰难。')
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
sys.exit(app.exec_())
这个程序在窗口中给出一个问题和两个答案,当你选择某个答案、按下对应的按钮后,状态栏里会显示出相应的评价,具体效果如下: