【问题标题】:PyQt clear outstanding user inputPyQt 清除未完成的用户输入
【发布时间】:2020-10-19 12:18:22
【问题描述】:

在我的程序中,我有一个按钮连接到一个需要大约 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_() )

【问题讨论】:

标签: python pyqt


【解决方案1】:

当一个 阻塞 函数在主 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(因此它不支持信号) ,并且你需要找到一种方法来通知主线程它的状态。

【讨论】:

  • 这解决了这里提出的问题,谢谢。我对该主题进行了一些搜索,另一个 stackoverflow 也有帮助。对于将来希望在传递参数和更新进度条时实现此功能的人,请访问:stackoverflow.com/questions/33138706/…
猜你喜欢
  • 2011-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-12
  • 1970-01-01
  • 2022-01-21
  • 2020-02-25
  • 1970-01-01
相关资源
最近更新 更多