PyQt清除未完成的用户输入



在我的程序中,我有一个按钮,它连接到一个运行大约需要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继承了而不是(因此它不支持信号(,并且您需要找到一种方法来通知主线程其状态。

最新更新