title: How to display a PyQt5/PySide2 dialog box while other processes run in the background
body: <p>Overview: I have a fairly large GUI program, built in PyQt5/PySide2 that performs a multitude of operations. Some processes are very quick and a few can take up to around a minute to complete. I had my program set up to display a sort of 'Please Wait...' dialog box for any process that took more than a second or so. I did this using the <code>threading</code> module in conjunction with the <code>signals</code> built into PyQt5/PySide2. While it worked fine, I found that I could not also run threading using <code>concurrent.futures</code>, because these threading modules did not function well together. <code>concurrent.futures</code> did not handle the signals and the <code>threading</code> was generally much slower when processing a multitude of functions back-to-back. So I take my pick when it is appropriate to use either one. </p>
  
  <p>Problem: However, I started experiencing instances where, though the dialog box would appear, it would not display the text within the box, which usually was a message along the lines of "PLEASE WAIT, PROCESSING REQUEST". Essentially, the process of displaying the text was/is being held up until the underlying process finishes. After the process finishes, as long as the window isn't closed, it would then show the text.</p>
  
  <p>Obstacle: In addition to the above described situation, I have re-written parts of the signal and dialog display classes, which on the surface, appear to function as expected and I have included the full code of a basic example. However, when I apply those exact methods to my larger program, it closes itself when it first begins to display the dialog box. </p>
  
  <p>Question: I am wondering if I am missing a basic element or concept in this below example, which when applied to a much bigger program, possibly creates some issues for me. I am looking for the potential red flags in this example.</p>
  
  <p>EDIT: When you run this example, click on the OK button to test the dialog box and threading. The 'main' box will disappear and only the dialog box should be displayed. After the 3 seconds are up, the dialog box disappears and another box appears. This closely resembles the actual functionality of my larger program. Essentially, when you start up you 'login' to the program, so the start menu disappears and then the actual program initializes and loads up. As you can see with this example, the box will display briefly then disappears and this is what happens in my program. A user logs in, but then within a second of logging in, the program closes. I have tried variations on how to get the window to load. The one listed below actually displays it at least, but other methods I've used will just result in a <code>QApplication::exec: Must be called from the main thread</code> error. I've tried a few other methods and listed them below, though obviously none of them work.</p>
  
  <pre><code>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()
  </code></pre>