Как отобразить диалоговое окно PyQt5 / PySide2, когда другие процессы выполняются в фоновом режиме - PullRequest
1 голос
/ 17 апреля 2020

Обзор: у меня есть довольно большая GUI программа, построенная на PyQt5 / PySide2, которая выполняет множество операций. Некоторые процессы очень быстрые, а некоторые могут занять около минуты. Моя программа была настроена для отображения своего рода диалогового окна «Пожалуйста, подождите ...» для любого процесса, который занял больше секунды или около того. Я сделал это, используя модуль threading вместе с signals, встроенным в PyQt5 / PySide2. Хотя это работало нормально, я обнаружил, что не могу также запустить многопоточность, используя concurrent.futures, потому что эти многопоточные модули не работают хорошо вместе. concurrent.futures не обрабатывал сигналы, а threading, как правило, был намного медленнее при одновременной обработке множества функций. Поэтому я выбираю, когда уместно использовать любой из них.

Проблема: Тем не менее, я начал испытывать случаи, когда, хотя диалоговое окно появлялось, оно не отображало текст внутри поля, который обычно представлял собой сообщение в виде строки «ПОЖАЛУЙСТА, ЖДУ, ОБРАБОТКА ЗАПРОСА». По сути, процесс отображения текста задерживался до тех пор, пока основной процесс не завершится. После завершения процесса, пока окно не закрыто, будет отображаться текст.

Препятствие: В дополнение к описанной выше ситуации, я переписал части сигнала и отображения диалогов классы, которые на первый взгляд кажутся функционирующими должным образом, и я включил полный код базового c примера. Однако, когда я применяю эти точные методы к моей более крупной программе, она закрывается, когда впервые начинает отображать диалоговое окно.

Вопрос: Мне интересно, отсутствует ли в этом примере элемент или концепция базового c, который при применении к гораздо более крупной программе может создать для меня некоторые проблемы. Я ищу потенциальные красные флаги в этом примере.

РЕДАКТИРОВАТЬ: Когда вы запускаете этот пример, нажмите кнопку ОК, чтобы проверить диалоговое окно и потоки. «Главное» окно исчезнет, ​​и должно отображаться только диалоговое окно. По истечении 3 секунд диалоговое окно исчезает, и появляется другое окно. Это очень похоже на реальную функциональность моей более крупной программы. По сути, при запуске вы входите в программу, поэтому меню «Пуск» исчезает, а затем программа инициализируется и загружается. Как вы можете видеть из этого примера, окно будет отображаться на короткое время, а затем исчезнет, ​​и это то, что происходит в моей программе. Пользователь входит в систему, но затем в течение секунды после входа программа закрывается. Я пробовал варианты того, как получить окно для загрузки. Приведенный ниже способ отображает это как минимум, но другие методы, которые я использовал, просто приведут к ошибке QApplication::exec: Must be called from the main thread. Я попробовал несколько других методов и перечислил их ниже, хотя, очевидно, ни один из них не работает.

import sys
from PySide2 import QtWidgets, QtCore
import PySide2
import time
import threading

def startThread(functionName, *args, **kwargs):
    startThread.t = threading.Thread(target=functionName)
    startThread.t.daemon = True
    startThread.t.start()

class UserInput(object):
    def setupUi(self, get_user_input=None):
        # Basic shape
        self.width = 175
        get_user_input.setObjectName("get_user_input")
        get_user_input.resize(175, self.width)

        # Grid layout for the buttons
        self.buttonLayoutGrid = QtWidgets.QWidget(get_user_input)
        self.buttonLayoutGrid.setGeometry(QtCore.QRect(10, 115, 155, 50))
        self.buttonLayoutGrid.setObjectName("buttonLayoutGrid")
        self.buttonLayout = QtWidgets.QGridLayout(self.buttonLayoutGrid)
        self.buttonLayout.setContentsMargins(0, 0, 0, 0)
        self.buttonLayout.setObjectName("buttonLayout")
        self.buttonLayout.setAlignment(PySide2.QtCore.Qt.AlignLeft|PySide2.QtCore.Qt.AlignVCenter)
        # Buttons
        self.buttonOK = QtWidgets.QPushButton(self.buttonLayoutGrid)
        self.buttonOK.setObjectName("buttonOK")
        self.buttonOK.setText("OK")

class FakeBox(PySide2.QtWidgets.QDialog):

    def __init__(self):
        super(FakeBox, self).__init__()
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

    def setupUi(self, box_details):
        box_details.setObjectName("box_details")
        box_details.resize(500, 89)
        self.labelProcessStatus = QtWidgets.QLabel(box_details)
        self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
        self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.labelProcessStatus.setWordWrap(True)
        self.labelProcessStatus.setObjectName("labelProcessStatus")
        self.buttonProcessCompleted = QtWidgets.QPushButton(box_details)
        self.buttonProcessCompleted.setEnabled(False)
        self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
        self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
        QtCore.QMetaObject.connectSlotsByName(box_details)

class FUNCTION_RUN(PySide2.QtWidgets.QDialog):
    display_dialog_window = PySide2.QtCore.Signal(str)
    display_process_complete = PySide2.QtCore.Signal(str)
    process_complete_no_msg = PySide2.QtCore.Signal()

    def __init__(self):
        super(FUNCTION_RUN, self).__init__()
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

    def setupUi(self, functionRunning):
        functionRunning.setObjectName("functionRunning")
        functionRunning.resize(234, 89)
        self.labelProcessStatus = QtWidgets.QLabel(functionRunning)
        self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
        self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.labelProcessStatus.setWordWrap(True)
        self.labelProcessStatus.setObjectName("labelProcessStatus")
        self.buttonProcessCompleted = QtWidgets.QPushButton(functionRunning)
        self.buttonProcessCompleted.setEnabled(False)
        self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
        self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
        QtCore.QMetaObject.connectSlotsByName(functionRunning)

    def show_msg(self, msg_text=None):
        self.setWindowTitle("RUNNING")
        self.labelProcessStatus.setText(msg_text)
        self.setModal(False)
        self.show()

    def process_complete(self, msg_text=None):
        self.setWindowTitle("FINISHED")
        self.labelProcessStatus.setText(msg_text)
        self.buttonProcessCompleted.setText('OK')
        self.buttonProcessCompleted.setEnabled(True)
        self.setModal(False)
        self.show()

    def process_finished(self):
        self.hide()

class UserInputPrompt(PySide2.QtWidgets.QDialog, UserInput):

    def __init__(self):
        super(UserInputPrompt, self).__init__()
        self.setupUi(self)
        self.buttonOK.clicked.connect(self.scoreMass)

    def some_more_text(self):
        print('Some more...')

    def scoreMass(self):
        startThread(MASTER.UI.display_msg)

    def display_msg(self):
        def dialog():
            MASTER.UI.hide()
            m = ' Processing things...'
            MASTER.processing_window.display_dialog_window.emit(m)
            MASTER.UI.some_more_text()
            time.sleep(3)
            MASTER.Second_UI.show()
            MASTER.processing_window.process_complete_no_msg.emit()    

        dialog()    

class MASTER(object):
    def __init__(self):
        super(MASTER, self).__init__()
        MASTER.UI = UserInputPrompt()
        MASTER.Second_UI = FakeBox()
        MASTER.processing_window = FUNCTION_RUN()
        MASTER.processing_window.display_dialog_window.connect(MASTER.processing_window.show_msg)
        MASTER.processing_window.display_process_complete.connect(MASTER.processing_window.process_complete)
        MASTER.processing_window.process_complete_no_msg.connect(MASTER.processing_window.process_finished)
        MASTER.UI.show()
        app.exec_()

def main(): 
    MASTER()

if __name__ == '__main__':
    global app
    app = PySide2.QtWidgets.QApplication(sys.argv)
    main()

1 Ответ

1 голос
/ 17 апреля 2020

Линия, где у вас есть MASTER.Second_UI.show() Вероятно, там, где вас задерживают. Вы создали экземпляр в своем основном потоке, и это хорошо, но вам нужно создать сигнал в этом классе, что вы можете испустить метод show(). Сделайте так, чтобы класс FakeBox выглядел следующим образом:

class FakeBox(PySide2.QtWidgets.QDialog):
    show_new_prompt = PySide2.QtCore.Signal()

    def __init__(self):
        super(FakeBox, self).__init__()
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

А затем в вашем классе MASTER выглядело так:

class MASTER(object):
    def __init__(self):
        super(MASTER, self).__init__()
        MASTER.UI = UserInputPrompt()
        MASTER.Second_UI = FakeBox()
        MASTER.Second_UI.show_new_prompt.connect(MASTER.Second_UI.show)
        # Keeping everything after this line

И, наконец, в вашей функции display_msg() , измените его следующим образом:

def display_msg(self):
    def dialog():
        MASTER.UI.hide()
        m = ' Processing things...'
        MASTER.processing_window.display_dialog_window.emit(m)
        MASTER.UI.some_more_text()
        time.sleep(3)
        MASTER.processing_window.process_complete_no_msg.emit()    
        MASTER.Second_UI.show_new_prompt.emit()

    dialog()     

Это должно следовать за последовательностью, как вы описали, и будет держать последнее окно, отображаемое в конце.

...