使用QThread在PyQt5中展示QProgressDialog

3

我正在使用PyQt5编写一个管理销售订单的应用程序。在创建或删除订单时,我想显示一个走马灯样式的进度对话框来指示应用程序正在工作。我查看了许多帖子,其中答案涉及使用QThread。我尝试实现它,但似乎我漏掉了一些东西。这是我的线程类。

class Worker(QThread):
    finished = Signal()

def run(self):
    self.x = QProgressDialog("Please wait..",None,0,0)
    self.x.show()

def stop(self):
    self.x.close()

在主窗口的init中,我创建了self.worker=Worker()。
现在删除条目的代码示例如下:
msg = MsgBox("yn", "Delete Order", "Are you sure you want to delete this order?") # Wrapper for the QMessageBox
if msg == 16384:
    self.worker.start()   ## start the worker thread, hoping to start the progress dialog
    session.delete(order) ##delete order from db
    session.commit()      ##commit to db
    self.change_view("Active", 8) ##func. clean up the table.
    self.worker.finished.emit()   ##emit the finished signal to close the progress dialog

无进度对话框显示。GUI 会在一两秒钟内冻结,然后条目删除而没有任何进度对话框显示。
抱歉,我的代码相当长,所以我无法在这里全部包含它,我只是想看看是否犯了什么严重的错误。

1
这是因为我只想在数据库更新完成之前显示繁忙指示。这是一个跑马灯样式的栏,因此最小和最大值都设置为零。 - ahendawy
1
请提供一个 [mcve]。 - S. Nick
你应该将删除订单的操作发送到线程中,而不是在QDialog的创建中进行。对话框应该在主线程中创建,并使用QThread.startedQThread.finished信号来显示/隐藏。 - Heike
@Heike 我认为最好将它分离到另一个线程中,这样我就可以在主窗口的其他功能中重复使用它,比如创建新订单或编辑订单。但从理论上讲,它应该没有任何阻止执行的东西。 - ahendawy
@Guimoute 我已经在主窗口的__init__方法中创建了工作线程实例。这是您所说的第一点吗? - ahendawy
显示剩余3条评论
1个回答

2
您的代码存在两个主要问题:
  1. GUI元素(所有继承或相关于QWidget子类的元素)必须仅从主Qt线程创建和访问。
  2. 假设需要一定时间的是删除/提交操作,那么应该将这些操作放在线程中,并在主线程显示进度对话框,而不是相反。此外,请注意,QThread已经有一个finished()信号,您不应该覆盖它。
这是基于您的代码的示例:
class Worker(QThread):
    def __init__(self, session, order):
        super.__init__()
        self.session = session
        self.order = order

    def run(self):
        self.session.delete(self.order)
        self.session.commit()


class Whatever(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)

    def deleteOrder(self, session, order):
        msg = MsgBox("yn", "Delete Order", 
            "Are you sure you want to delete this order?")
        if msg == MsgBox.Yes: # you should prefer QMessageBox flags
            self.worker = Worker(session, order)
            self.worker.started(self.progressDialog.show())
            self.worker.finished(self.deleteCompleted)
            self.worker.start()

    def deleteCompleted(self):
        self.progressDialog.hide()
        self.change_view("Active", 8)

由于进度对话框在处理过程中应保持打开状态,因此您还应防止用户能够关闭它。为此,您可以在其上安装事件过滤器,并确保任何关闭事件都被接受;此外,由于QProgressDialog继承自QDialog,因此应该过滤掉Esc键,否则它将不会关闭对话框,而是会拒绝并隐藏它。
class Whatever(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)
        self.progressDialog.installEventFilter(self)

    def eventFilter(self, source, event):
        if source == self.progressDialog:
            # check for both the CloseEvent *and* the escape key press
            if event.type() == QEvent.Close or event == QKeySequence.Cancel:
                event.accept()
                return True
        return super().eventFilter(source, event)

这对理解线程非常有帮助。然而,遵循这种哲学意味着我必须为每个处理数据库的函数创建线程。因此,最终主窗口功能将被分成各种独立的线程。这是一个好的做法吗? - ahendawy
任何可能会阻塞的操作通常都会被移动到单独的线程中。请注意,您应该尝试优化它们的使用,避免不必要的创建(您可以重用线程,只需每次正确设置即可)。此外,请查看QRunnable和QThreadPool。 - musicamante

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接