在我的程序中,我有一个按钮,它连接到一个运行大约需要15秒的函数。我希望在此运行期间忽略所有用户输入。按下btn
时,调用以下功能:
def btn_call(self) :
self.btn.setEnable(False) # disables the button and shows it greyed out
fn() # some function that takes ~15 seconds to run
self.btn.setEnable(True) # re-enables the button
希望在fn()
运行时,程序不会对btn
按压做出响应。当前,如果在运行fn()
时按下btn
,则每次按下btn
时都会运行fn()
。
有没有办法清除fn()
运行时发生的所有用户输入?
编辑:添加了MWE。如果单击Run Function.
,该功能将开始。如果在Run Function.
仍在运行时单击它,该函数将再次运行。这是我想制止的行为。
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
from time import sleep
def fn(num) :
for i in range(num) :
sleep(0.5)
yield i
class MainWindow(QWidget) :
def __init__(self) :
super().__init__()
self.layout = QVBoxLayout(self)
self.btn_fn = QPushButton("Run function.")
self.btn_fn.clicked.connect(self.run_fn)
self.layout.addWidget(self.btn_fn)
self.prog_bar = QProgressBar()
self.layout.addWidget(self.prog_bar)
self.show()
def run_fn(self) :
self.btn_fn.setEnabled(False)
num = 20
self.prog_bar.setValue( 0 )
for i in fn(num) :
self.prog_bar.setValue( 100*(i+1)/num )
self.btn_fn.setEnabled(True)
if __name__ == '__main__' :
app = QApplication(sys.argv)
window = MainWindow()
sys.exit( app.exec_() )
当阻塞函数在主Qt线程中运行时,结果是所有事件都在该线程中排队所有事件,包括重新绘制和鼠标事件。
不过,QApplication仍将接收来自操作系统的传入事件,并将它们添加到其事件队列中,直到阻塞函数返回。
这导致以下重要方面:
- Qt侧所有要求时间的操作将被阻止,直到控制返回到Qt事件循环
- 如果涉及动画,则不会正确完成小部件的重新绘制(因此,由于前一点,即使没有,按钮也可能看起来是启用的(
- 小部件可能接收到的所有键盘/鼠标事件将被排队,并且只有在控件返回到主Qt事件循环时才被实际处理
理论上可以做的是,在计时功能即将启动时立即阻止按钮上的所有信号,然后使用单触发QTimer(确保其timeout
调用在所有Qt队列(包括定时事件(被清除后立即得到处理(来取消阻止信号发射。
这在以下示例中很清楚:
from PyQt5 import QtCore, QtWidgets
from time import sleep
def veryLongFunction():
sleep(5)
def restoreButton():
label.setText('Idle')
button.blockSignals(False)
button.setEnabled(True)
def start():
label.setText('Working...')
button.setEnabled(False)
button.blockSignals(True)
QtWidgets.QApplication.processEvents()
veryLongFunction()
QtWidgets.QApplication.processEvents()
QtCore.QTimer.singleShot(0, restoreButton)
import sys
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget()
button = QtWidgets.QPushButton('Start long function')
label = QtWidgets.QLabel('Idle', alignment=QtCore.Qt.AlignCenter)
layout = QtWidgets.QVBoxLayout(widget)
layout.addWidget(button)
layout.addWidget(label)
geo = QtCore.QRect(0, 0, 160, 80)
geo.moveCenter(app.primaryScreen().availableGeometry().center())
widget.setGeometry(geo)
widget.show()
button.clicked.connect(start)
sys.exit(app.exec_())
在上面的示例中,发送到该按钮的任何鼠标单击都将被忽略,直到该按钮再次启用,这从技术上解决了您的问题。
但是,不,这不是一个好的方法:这是一个非常糟糕的方法
在不支持合成或双重缓冲先前绘制的设备的平台上,您不仅会遇到更新问题(在处理时移动窗口会显示图形工件(,而且最重要的是,这不是正确的方法。
只要";"耗时";进程只能单独完成(意味着不需要并发处理(,QThread是最好的选择:
# ...
class VeryLongFunctionThread(QtCore.QThread):
def run(self):
sleep(5)
def start():
label.setText('Working...')
button.setEnabled(False)
veryLongFunction.start()
def end():
label.setText('Idle')
button.setEnabled(True)
# ...
button.clicked.connect(start)
veryLongFunction = VeryLongFunctionThread()
veryLongFunction.finished.connect(end)
# ...
在处理需要同时运行多次的情况下,另一种选择是使用QRunnable,但请记住,它确实从QObject继承了而不是(因此它不支持信号(,并且您需要找到一种方法来通知主线程其状态。