Overview: I have a GUI-based program that has a multitude of functionality built into buttons and menu selection items. Many of the processes are instantaneous, but several of them can easily take more than 5, 10 or even 20 seconds.
Current Problem: While I have a dialog box displaying, it will not display the text inside of the box, such as 'Completing Process, please wait...'
Goal: While a long processes run, I would like to display a 'please wait' dialog box, so that the user knows the program is not stalled and is working on the function the user selected.
Background info: The GUI is built in PySide2 (aka PyQt5) and Python 3.6.5
Current Attempts:
Method 1 - Initially, I used the threading
module to initiate the primary function to be run. At the start of the function, I would have a call to show the dialog box, which would display as intended and the rest of the function would process/complete in the background.
Method 1 Problem - This worked great until the program became more complex and I needed to use ThreadPoolExecutor to speed up things. ThreadPoolExecutor and the threading module do not work well, if at all together and will result in a crash without an error message, so I had to abandon that method.
Method 2 - I tried using the setModal
function of the QDialog box and called the exec_
function
Method 2 Problem - The dialog box would show and display the text as desired, but setModal(False) had no effect and the processes would be halted until the window was closed.
Method 3 (current method used) - In the initialization of my ProcessRunning dialog window class, I have created a PySide2 signal, which takes in a string (the string being the message that I want to display). The preceeding line before a call to a long process, I called the emit()
function of my signal, connected to which is the function to display the dialog box.
Method 3 Problem - The window displays and the background process runs but the window doesn't display the text, almost like that part of the process is held up by the background process.
Summary: While the question title suggests I don't know how to display a QDialog box, the real problem is displaying the text inside the window which instructs the user to "wait." I feel that since this isn't happening with my current method, I am not displaying the box "correctly". So, the "correct" method to achieve this is what I am in search of.
Here's a "short" example using concepts I have in my full program. :
import sys
import math
import PySide2
from PySide2 import QtCore, QtGui, QtWidgets
import pandas
class Ui_functionRunning(object):
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")
class FunctionRunning(PySide2.QtWidgets.QDialog, Ui_functionRunning):
display_box = PySide2.QtCore.Signal(str)
def __init__(self):
super(FunctionRunning, self).__init__()
self.setupUi(self)
def showDialog(self, displayText):
MasterClass.process_running.labelProcessStatus.setText(displayText)
MasterClass.process_running.show()
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(335, 255, 126, 23))
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
MainWindow.setStatusBar(self.statusbar)
self.pushButton.setText("PUSH TO TEST ")
class MainWindowUI(PySide2.QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindowUI, self).__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.test_function_running)
def test_function_running(self):
MasterClass.process_running.display_box.emit('testing running and displaying a message')
df = pandas.DataFrame({'id': [0,1,2,3,4,5,6,7,8,9],'lon':[-121.28473, -121.29511, -121.32834, -121.29569, -121.29251, -121.25374, -121.28417, -121.29854, -121.21188, -121.25812], 'lat':
[37.986450, 37.911396, 37.969345, 37.923443, 37.990696, 37.975395, 37.942062, 37.993350, 37.979430, 37.975790]})
`If your system processes all this too quickly to notice the problem, increase the value in
`this first loop, from 10 to something like 20 or 30
for x in range(10):
for i in df.index:
for r in df.index:
if df.loc[i, 'lat'] != '':
df.loc[i, 'DIST_to_%s' % df.loc[i, 'id']] = self.dist(float(df.loc[i, 'lat']), float(df.loc[i, 'lon']), float(df.loc[r, 'lat']), float(df.loc[r, 'lon']))
print('%s pass completed - %s times through' % (i, x))
print('finished calculating distances')
MasterClass.process_running.labelProcessStatus.setText('All Done!')
def dist(self, lat_1, lon_1, lat_2, lon_2):
if lat_1 != lat_2 and lon_1 != lon_2:
val_1 = math.radians(90 - float(lat_1))
val_2 = math.cos(val_1)
val_3 = math.radians(90 - float(lat_2))
val_4 = math.cos(val_3)
val_5 = math.radians(90 - float(lat_1))
val_6 = math.sin(val_5)
val_7 = math.radians(90 - float(lat_2))
val_8 = math.sin(val_7)
val_9 = math.radians(float(lon_1) - float(lon_2))
val_10 = math.cos(val_9)
distance = round(math.acos(val_2 * val_4 + val_6 * val_8 * val_10) * 3958.756, 1)
return distance
else:
return 0
class MasterClass:
def __init__(self):
super(MasterClass, self).__init__()
MasterClass.app = PySide2.QtWidgets.QApplication(sys.argv)
MasterClass.process_running = FunctionRunning()
MasterClass.process_running.display_box.connect(MasterClass.process_running.showDialog)
MasterClass.main_ui = MainWindowUI()
MasterClass.main_ui.show()
MasterClass.app.exec_()
if __name__ == '__main__':
MasterClass()
The Method 1 you reference may work but with a few considerations; any call to update your UI, should be done using signals, via
PySide2.QtCore.Signal()
Change your
MainWindowUI
class to look something like this (keep yourdist
function, no changes needed there):Now update your
MasterClass
to look like this:This should help with using threads with PySide2 and also with problems of
threading
andThreadPoolExecutor
running into issues. So the key takeaway here, is use a signal when you need a process to be run in the main thread, such as an update to the UI. It can be used for other things, but is only absolutely necessary when the UI needs to be refreshed.