赞
踩
本文翻译自:PyQt5 Signals, Slots & Events
(1) Signals和Slots
Signals是widget在某些事情发生时发出的通知。这可以是任何数量的东西,从按下按钮,到输入框的文本变化,到窗口的文本变化。许多信号是由用户操作发起的,但这不是规则。除了通知发生的事情外,信号还可以发送数据以提供有关发生的事情的其他上下文。
Slots是Qt中Signals的接收器,在Python中你的应用的任何函数或方法都能充当Slot,简单的把Signal和它联系在一起就行。如果Signal发送数据,接收函数也将接收数据,许多Qt widget也有它们自己的内置slot。
让我们来看看Qt Signal的基础知识,以及如何使用它们来连接小部件以使事情在您的应用程序中发生.
(2) QPushButton Signals
现在把我们在上篇中的QPushButton和一个自定义的Python方法绑定起来,我们创建一个简单的slot,名为the_button_was_clicked()来接收QPushButton的clicked信号:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") button = QPushButton("Press Me!") button.setCheckable(True) button.clicked.connect(self.the_button_was_clicked) # Set the central widget of the Window. self.setCentralWidget(button) def the_button_was_clicked(self): print("Clicked!") app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
(3) 接收数据
这是一个好的开始!我们已经听说,Signal也可以发送数据,以提供有关刚刚发生的事情的更多信息。.clicked也是不例外的,也为button提供选中或切换状态:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") button = QPushButton("Press Me!") button.setCheckable(True) button.clicked.connect(self.the_button_was_clicked) button.clicked.connect(self.the_button_was_toggled) self.setCentralWidget(button) def the_button_was_clicked(self): print("Clicked!") def the_button_was_toggled(self, checked): print("Checked?", checked) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
(4) 接收数据
在一个Python的变量中,存储一个widget的当下状态通常是有用的。这允许您像任何其他Python变量一样使用这些值,而无需访问原始widget。你可以将他们保存成单个变量或者你喜欢的话也可以用dictionary。
下面的例子中,我们把我们的button的checked值存入了变量self.button_is_checked:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button_is_checked = True self.setWindowTitle("My App") button = QPushButton("Press Me!") button.setCheckable(True) button.clicked.connect(self.the_button_was_toggled) button.setChecked(self.button_is_checked) self.setCentralWidget(button) def the_button_was_toggled(self, checked): self.button_is_checked = checked print(self.button_is_checked) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
首先,我们为变量设置默认值 (为True),然后使用默认值设置widget的初始状态。当widget状态改变时,我们接收信号并更新变量以匹配。
你可以将同样的形式用在任何PyQt的Widget上。如果Widget不提供发送当前状态的signal,则需要直接在处理程序中从Widget检索值:
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button_is_checked = True self.setWindowTitle("My App") self.button = QPushButton("Press Me!") self.button.setCheckable(True) self.button.released.connect(self.the_button_was_released) self.button.setChecked(self.button_is_checked) self.setCentralWidget(self.button) def the_button_was_released(self): self.button_is_checked = self.button.isChecked() print(self.button_is_checked)
(5) 更改界面
让我们按下按键的时候,界面上能发生改变怎么样?让我们更新我们的slot方法来修改按钮,更改文本并禁用按钮,使其不再可点击:
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") self.button = QPushButton("Press Me!") self.button.clicked.connect(self.the_button_was_clicked) self.setCentralWidget(self.button) def the_button_was_clicked(self): self.button.setText("You already clicked me.") self.button.setEnabled(False) # Also change the window title. self.setWindowTitle("My Oneshot App")
运行结果:
因为我们需要在我们的the_button_was_clicked方法中来访问我们的button。我们self中引用button,通过向.setText()传递字符串来改变button的文本,通过向.setEnabled()中传递False来取消按键。
你可以在你的slot方法中执行任何操作。大多数的Widget都有他们自己的信号,用于我们自己窗口的QMainWindow也不例外。
在下面的例子中,我们将QMainWindow的.windowTitleChanged信号与slot方法the_window_title_changed联系起来:
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton import sys from random import choice window_titles = [ 'My App', 'My App', 'Still My App', 'Still My App', 'What on earth', 'What on earth', 'This is surprising', 'This is surprising', 'Something went wrong' ] class MainWindow(QMainWindow): def __init__(self): super().__init__() self.n_times_clicked = 0 self.setWindowTitle("My App") self.button = QPushButton("Press Me!") self.button.clicked.connect(self.the_button_was_clicked) self.windowTitleChanged.connect(self.the_window_title_changed) # Set the central widget of the Window. self.setCentralWidget(self.button) def the_button_was_clicked(self): print("Clicked.") new_window_title = choice(window_titles) print("Setting title: %s" % new_window_title) self.setWindowTitle(new_window_title) def the_window_title_changed(self, window_title): print("Window title changed: %s" % window_title) if window_title == 'Something went wrong': self.button.setDisabled(True) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
在上面的案例中,我们设置了一个窗口标题的列表,我们将会使用Python内置的random.choice( )从这个列表中随机挑选一个,我们将我们自定义的slot方法the_window_title_changed和窗口的.windowTitleChanged信号绑定了。
有两点需要注意
首先,当设置窗口标题的时候,信号windowTitleChanged并不总是发出,信号只有在新标题不同于旧标题的时候才会发出。如果你好几次都设置了相同的标题,那么信号只会在第一次的时候发出。仔细检查信号触发的条件非常重要,以避免在应用程序中使用它们时感到惊讶。
第二,请注意我们如何使用信号将事物联系在一起。按下按键这一件事情的发生,可以反过来引发多个其他事件的发生。这些后续影响不需要知道是什么导致了它们,而只是简单地遵循简单规则的结果。
(6) 直接将Widget联系在一起
你并不总是需要用一个Python函数去处理signal,你也可以直接把Qt Widget和另一个连接起来。
下面的这个案例中,我们在窗口中增加了QLineEdit和QLabel,在窗口的__init__部分中,我们把我们line edit的.textChanged信号和QLabel的.setText方法联系了起来,现在只要QLineEdit的文本内容发生了改变,QLabel的.setText方法就会收到文本:
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget import sys class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") self.label = QLabel() self.input = QLineEdit() self.input.textChanged.connect(self.label.setText) layout = QVBoxLayout() layout.addWidget(self.input) layout.addWidget(self.label) container = QWidget() container.setLayout(layout) # Set the central widget of the Window. self.setCentralWidget(container) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
注意,为了把输入和标签联系起来,输入与标签都应该被定义,代码把这两个widget都放进了一个layout中,并将他们设置在窗口上。
(7) Event
每一次用户和Qt应用之间的一次交互都是一个Event,这里有许多种类的event,每一种代表了一种不同的交互。Qt使用event对象代表了这些事件,这些event对象承载了“发生了什么”的信息。当交互发生时,这些event被发送到了特定的event处理程序中。
通过定义自定义或扩展事件处理程序,您可以更改widget对这些event的响应方式。事件处理程序的定义就像其他任何方法的定义一样,但名称必须针对它们所处理事件的种类。
widget所收到的主要event之一是QMouseEvent。QMouseEvent是为了每一次鼠标移动和对widget进行按键点击而创造的,下列事件处理程序都能用来处理鼠标事件:
例如,点击一个widget将会引发一个QMouseEvent,对被发送到这个widget的事件处理程序.mousePressEvent进行处理。此处理程序可以使用事件对象来查找有关所发生事件的信息,例如触发事件的原因以及事件发生的具体位置。
你可以通过子类化或重写类中的处理方法拦截event。你能选择筛选、修改或忽略事件,通过使用super() 调用父类函数将它们传递给事件的正常处理程序。下面的例子:
import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label = QLabel("Click in this window") self.setCentralWidget(self.label) def mouseMoveEvent(self, e): self.label.setText("mouseMoveEvent") def mousePressEvent(self, e): self.label.setText("mousePressEvent") def mouseReleaseEvent(self, e): self.label.setText("mouseReleaseEvent") def mouseDoubleClickEvent(self, e): self.label.setText("mouseDoubleClickEvent") app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
你将会注意到,只有你将鼠标按键按下的时候,鼠标移动事件才会被登记。你可以通过在窗口中调用self.setMouseTracking(True)来改变它。通常要记录用户的点击,您应该观察鼠标按下和释放。
在事件处理程序中,您可以访问事件对象。此对象包含有关事件的信息,可用于根据确切发生的情况做出不同的响应。接下来我们将查看鼠标事件对象。
(8) Mouse events
Qt中的所有鼠标事件都使用QMouseEvent对象进行跟踪,有关该事件的信息可从以下事件方法中读取。
你可以使用一个事件处理程序中的这些方法去以不同的方式响应不同的事件。位置方法将全局和局部 (相对于Widget) 位置信息作为QPoint对象提供,而按钮则使用Qt命名空间中的鼠标按钮类型进行报告。
下面的案例允许我们以不同的方式回应对屏幕的左击、右击和中间点击:
import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QLabel class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label = QLabel("Click in this window") self.setCentralWidget(self.label) def mouseMoveEvent(self, e): self.label.setText("mouseMoveEvent") def mousePressEvent(self, e): if e.button() == Qt.LeftButton: self.label.setText("mousePressEvent LEFT") elif e.button() == Qt.MiddleButton: self.label.setText("mousePressEvent MIDDLE") elif e.button() == Qt.RightButton: self.label.setText("mousePressEvent RIGHT") def mouseReleaseEvent(self, e): if e.button() == Qt.LeftButton: self.label.setText("mouseReleaseEvent LEFT") elif e.button() == Qt.MiddleButton: self.label.setText("mouseReleaseEvent MIDDLE") elif e.button() == Qt.RightButton: self.label.setText("mouseReleaseEvent RIGHT") def mouseDoubleClickEvent(self, e): if e.button() == Qt.LeftButton: self.label.setText("mouseDoubleClickEvent LEFT") elif e.button() == Qt.MiddleButton: self.label.setText("mouseDoubleClickEvent MIDDLE") elif e.button() == Qt.RightButton: self.label.setText("mouseDoubleClickEvent RIGHT") app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Qt名称空间的button标识符如下:
(9) 上下文菜单
上下文菜单是小的上下文相关菜单,通常在右键单击窗口时出现。Qt支持这些菜单,并且Widget有具体的的event用来引发它们。在接下来的例子中我们将会拦截QMainWindow的.contextMenuEvent。每当要显示上下文菜单时,都会触发此事件,并且把一个单独的值event发送给QContextMenuEvent。
要拦截事件,我们只需使用同名的新方法覆盖对象方法。所以在这个例子中我们能够在我们的MainWindow子类创造一个方法,新方法名为contextMenuEvent,将会接收这种类型的所有event。
import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QAction, QApplication, QLabel, QMainWindow, QMenu class MainWindow(QMainWindow): def __init__(self): super().__init__() def contextMenuEvent(self, e): context = QMenu(self) context.addAction(QAction("test 1", self)) context.addAction(QAction("test 2", self)) context.addAction(QAction("test 3", self)) context.exec(e.globalPos()) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
运行结果:
为了完整起见,实际上有一种基于信号的方法来创建上下文菜单。
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.show()
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.on_context_menu)
def on_context_menu(self, pos):
context = QMenu(self)
context.addAction(QAction("test 1", self))
context.addAction(QAction("test 2", self))
context.addAction(QAction("test 3", self))
context.exec(self.mapToGlobal(pos))
(10) Event层次结构
在PyQt中,每个小部件都是两个不同层次结构的一部分: Python对象层次结构和Qt布局层次结构。响应或忽略事件的方式会影响UI的行为。
1.Python继承转发
通常你可能想要拦截一个事件,用它做一些事情,但仍然触发默认的事件处理行为。如果你的对象是继承自标准的widget, 它可能默认实施敏感的行为。你可以通过使用super()来调用父级别的实现从而引发这种行为。
注意,是Python的父类:
def mousePressEvent(self, event):
print("Mouse pressed!")
super().contextMenuEvent(event)
事件将继续表现正常,但你已经添加了一些非干扰行为。
2.Layout转发
当你增加了一个widget到你的应用时,它也从Layout中获得了另外的parent。通过调用.parent( )可以找到widget的parent。多数情况下是自动的,有的时候你必须手动指定这些parent,比如QMenu或QDialog。比如当你在主窗口中增加了一个widget的时候,主窗口就是widget的parent。
当event被创造出来用于与UI的用户交互时,这些event会被提交给UI最上面的widget。所以,如果你在窗口上有一个button,并点击button,那么button将会首先收到event。
如果第一个Widget无法处理该事件,或者选择不处理该事件,则该事件将向上到父widget,该父widget将被轮到。这种向上的过程一直持续到嵌套的widget,直到事件被处理或到达主窗口。
在你自己的事件处理程序中,你可以选择调用.accept( )来标记事件已处理:
class CustomButton(QPushButton)
def mousePressEvent(self, e):
e.accept()
或者,你可以通过在event对象中调用.ignore( )来标记事件未处理,在这种情况下,事件将继续在层次结构中向上转移。
如果你想让你的widget对事件透明,您可以安全地忽略您实际已经以某种方式响应的事件。类似的,你可以选择接收你不回应的事件为了让它们沉默。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。