0

我正在使用 PySide (Qt) Gui,它应该在单击按钮时退出循环。PySide 的按钮单击会发出一个信号,该信号会调用连接的函数。然而,当信号调用函数时,它使用它自己的系统进行错误处理。

我真的不想修复那个信号错误处理。

有没有办法在不引发错误的情况下打破“with”语句。

import sys
import time
from PySide import QtGui, QtCore

class MyClassError(Exception): pass

class MyClass(QtGui.QProgressDialog):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Properties
        self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint)
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.setMinimumDuration(2000) # if progress is finished in 2 sec it wont show
        self.setRange(0, 99)
    # end Constructor

    def breakOut(self):
        print("break out")
        self.__exit__(MyClassError, "User cancel!", "") # does not break out of "with"
        raise MyClassError("User cancel!") # PySide just prints Exception
    # end breakOut

    def __enter__(self):
        self.canceled.connect(self.breakOut)
        return self
    # end enter (with)

    def __exit__(self, etype, value, traceback):
#         self.canceled.disconnect(self.breakOut)
        if etype is None or etype == MyClassError:
            return True
        raise
    # end exit
# end class MyClass

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    window = QtGui.QMainWindow()
    window.show()

    myinst = MyClass()
    window.setCentralWidget(myinst)

    val = 0
    with myinst:
        for i in range(100):
            myinst.setLabelText(str(i))
            myinst.setValue(i)
            val = i+1
            time.sleep(0.1)
    # end with
    print(val)

    sys.exit(app.exec_())

我在信号中看到的 PySide/Qt 错误处理的修复看起来不太好。它覆盖了 QApplication,这意味着使用此类的其他人必须使用新的 QApplication。

PySide/Qt 信号错误处理是否有一种简单的方法,或者是否有另一种方法可以打破“with”语句。

4

2 回答 2

2

首先,我将您对@dano 回答的评论解释为您想在with语句中执行某种长时间运行的操作。

我认为你有一些基本的误解。

  1. 您的with声明(以及其中的代码)在调用app.exec_(). app.exec_()Qt 事件循环在被调用之前不会开始运行。因此,您的所有with语句正在做的就是排队等待事件循环启动后要处理的事件。因此,取消with语句中代码的 qt 信号将永远不会起作用,因为在事件循环启动之前不会处理信号,这是您的循环完成之后(至少在您上面提供的测试用例中(.

    解决此问题的方法是将您的语句放在由 QT 事件循环执行的回调中(例如,由 a或类似with排队的函数)QTimer

  2. 即使假设您修复了上述问题,您也永远无法通过单击按钮(或任何类型的信号)来中断长时间运行的任务,因为长时间运行的任务与 Qt 事件循环位于同一线程中,因此 Qt在您长时间运行的任务完成之前,事件循环无法处理任何新命令(例如按下取消按钮)。虽然您的长期任务是

    要解决这个问题,您需要将长时间运行的任务放在一个线程(python 线程或QThread)或另一个进程中。然而,这些方法也有其自身的困难,主要集中在这样一个事实,即您永远不应该尝试直接从线程更新 GUI。有一些选项可以解决这个问题,请参阅此处此处了解从线程安全更新 GUI 的问题。如果有帮助,我已将其中一些内容包含在一个名为qtutils的库中。

我意识到这并不能直接解决您关于如何中断with语句的问题,但是我希望我已经明确表示您当前的方法行不通。如果您切换到线程或进程,您应该能够立即终止线程/进程(如果您愿意),而不是等待线程读取条件并自行退出(当然,硬杀死某些东西可能会产生意想不到的副作用,但既然这是你似乎想要做的,我假设你已经考虑过这一点并决定它总是没问题的)。

于 2014-07-23T00:52:26.973 回答
0

您可以检查它是否已在循环本身中被取消:

class MyClass(QtGui.QProgressDialog):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.cancelled = False
        self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint)
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.setMinimumDuration(2000) # if progress is finished in 2 sec it wont show
        self.setRange(0, 99) 

    def breakOut(self):
        print("break out")
        self.cancelled = True

    def __enter__(self):
        self.canceled.connect(self.breakOut)
        return self

    def __exit__(self, etype, value, traceback):
        self.canceled.disconnect(self.breakOut)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    window = QtGui.QMainWindow()
    window.show()

    myinst = MyClass()
    window.setCentralWidget(myinst)

    val = 0 
    with myinst:
        for i in range(100):
            if myinst.cancelled:
                break
            myinst.setLabelText(str(i))
            myinst.setValue(i)
            val = i+1 
            time.sleep(0.1)

引发异常breakOut不会产生您想要的影响,因为它是在与for循环完全不同的上下文中调用的。无论 PySide 的错误处理如何,调用__exit__都不会发表声明。with我认为你的因果关系倒过来了:__exit__当你离开 with 块时被调用,调用__exit__不会强制with块中止。

于 2014-07-22T16:41:23.657 回答