Использование QThread для отображения QProgressDialog в PyQt5

Я использую PyQt5 для написания приложения, которое управляет заказами на продажу. При создании или удалении Заказа я хочу отобразить диалоговое окно прогресса в стиле marqee, чтобы указать, что приложение работает. Я посетил много постов, в которых ответ касался использования 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()

В инициализации главного окна я создаю 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

В результате диалоговое окно прогресса не отображается. Графический интерфейс просто зависает на секунду или две, а затем запись удаляется без отображения диалогового окна прогресса.

Извините, мой код довольно длинный, поэтому я не мог включить его сюда, я просто хотел посмотреть, не понял ли я что-то ужасное.

1 ответ

Решение

В вашем коде есть две основные проблемы:

  1. Элементы графического интерфейса (все, что унаследовано или связано с подклассом 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)
Другие вопросы по тегам