I do run some time consuming calculations and want to show a splash screen while the process is running. I've tried:
form = QSpashScreen()
form.showMessage('Please wait ...')
form.show()
time_consuming_function()
form.close()
But the splash screen doesn't actually appear. How can I make this work?
You need to specify one parent for QSplashScreen, otherwise it will map the constructor of this in native Qt:
QSplashScreen::QSplashScreen ( const QPixmap & pixmap = QPixmap(), Qt::WindowFlags f = 0 )
Construct a splash screen that will display the pixmap.
There should be no need to set the widget flags, f, except perhaps Qt::WindowStaysOnTopHint.
If you don't have a parent widget for this, you can assign the QDesktopWidget as the parent
from PyQt4.QtGui import *
import time
def time_consuming_function():
time.sleep(10)
def main():
app = QApplication(sys.argv)
form = QSplashScreen(QDesktopWidget().screen())
form.showMessage('Please wait ...')
form.resize(1000, 1000)
form.move(0, 0)
form.show()
time_consuming_function()
form.close()
app.exec_()
if __name__ == '__main__':
import sys
main()
By the way, you should not use the "tiem_consuming_function" with the same thread of main, this will block the UI thread, make the UI thread unable to respond the user input, looks like the UI is freezing.
Related
I made an overlay in wxpython which I use to display a bitmap on my computer. However, when I integrate it into an app that takes focus on a screen and moves my mouse, it blocks the app and never goes in the "apploop". If I comment the Overlay in the app constructor, the app works just fine but I wouldn't see the Overlay.
I tried threading the wx_app.Mainloop (I would comment self.wx_app.MainLoop()) and the apploop works fine but it now freezes the windows that's focused by the app so it's useless. Other processes like Chrome work fine.
How would I make the focused windows not blocked while having the overlay? I would need both of them at the same time.
Here's a simplified example of my code:
import win32gui, win32process, win32api
import wx
import threading
class App():
def __init__(self) -> None:
# Window focus
hwnd = win32gui.FindWindow(None, 'Exact window name')
win32gui.SetActiveWindow(hwnd)
remote_thread, _ = win32process.GetWindowThreadProcessId(hwnd)
win32process.AttachThreadInput(win32api.GetCurrentThreadId(), remote_thread, True)
prev_handle = win32gui.SetFocus(hwnd)
# Overlay
self.wx_app = wx.App()
self.overlay = Overlay()
self.wx_app.MainLoop()
# t = threading.Thread(target=self.wx_app.MainLoop)
# t.start()
self.apploop()
def apploop(self):
import time
while True:
print('Apploop running')
time.sleep(5)
class Overlay(wx.Frame):
def __init__(self):
style = ( wx.CLIP_CHILDREN | wx.STAY_ON_TOP | wx.NO_BORDER | wx.FRAME_SHAPED )
wx.Frame.__init__(self, None, title='Fancy', style = style)
self.bitmap = wx.StaticBitmap(parent=self)
self.SetSize( (320, 180) )
self.SetPosition( (10,10) )
self.SetBackgroundColour( [0,0,0,255] )
self.SetTransparent( 200 )
self.Show(True)
You were almost there. The wx.App has to be started in the main Python thread and cannot run in threading.Thread. So your long-running-code will work when running wx_app.MainLoop() in the main thread and your payload in a Thread:
self.wx_app = wx.App()
self.overlay = Overlay()
t = threading.Thread(target=self.apploop)
t.start()
self.wx_app.MainLoop()
See at the end of wx Wiki - LongRunningTasks for a very simple example, mind the wx.CallAfter or other thread-safe Python objects to communicate between worker and main thread.
My tkinter app windows are not closing properly. I am using python 3.6.6 with tkinter 8.6
My code does basically this:
Open a process, where:
A test function is called via a thread that closes Gui window after 3s
Gui window is created
Wait for thread to complete (join) and guess window was closed
I tried to use:
quit -> window only closes when i hover my mouse over it
destroy -> destroy does not return
I stripped it down to the following code, please copy & execute and/or tell me whats wrong...
from time import sleep, time
import threading
from multiprocessing import Process, set_start_method
from tkinter import *
CtrlApplObj = None
def Start():
global CtrlApplObj
CtrlApplObj = None
CtrlApplObj = ControlApplication()
CtrlApplObj.run()
def End():
print("Quit now...")
#CtrlApplObj.root.destroy()
CtrlApplObj.root.quit()
class ControlApplication():
def __init__(self):
pass
def run(self):
self.root=Tk()
print("Mainloop...")
self.root.mainloop()
def test():
sleep(3)
End()
def execute():
T1 = threading.Thread(target=test)
T1.start()
Start()
T1.join()
if __name__ == "__main__":
set_start_method("spawn")
for i in range(2):
TestProcess = Process(target=execute)
TestProcess.start()
TestProcess.join()
My final solution was not using any tkinter operations in test thread. Then destroy worked.
I had another problem with my test process not closing. This was because a queue was not empty. That porevented process to close.
I wrote simple program which has pyQt interface with 2 buttons (start and cancel). Start button runs some calculations in the background (by starting update function) and thanks to threading I can still use UI.
But the application crashes after 10sec - 2 minutes. UI just dissapears, program shutdown.
when I use pythonw to run app without console thread crashes after ~25 sec but gui still works.
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.show()
def update(self,stop_event):
while True and not stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
self.guiUpdate()
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update, args=(self.stop_event,))
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
if __name__ == '__main__':
main()
I dont know if I'm doing threading right. I found example like this on stack. I'm quite new to python and I'm using threading for the first time.
It is most likely due to calling a GUI function in your separate thread. PyQt GUI calls like setText() on a QLineEdit are not allowed from a thread. Anything that has PyQt painting outside of the main thread will not work. One way to get around this is to have your thread emit a signal to update the GUI when data is ready. The other way is to have a timer periodically checking for new data and updating the paintEvent after a certain time.
========== EDIT ==========
To Fix this issue I created a library named qt_thread_updater. https://github.com/justengel/qt_thread_updater This works by continuously running a QTimer. When you call call_latest the QTimer will run the function in the main thread.
from qt_thread_updater import get_updater
lbl = QtWidgets.QLabel('Value: 1')
counter = {'a': 1}
def run_thread():
while True:
text = 'Value: {}'.format(counter['a'])
get_updater().call_latest(lbl.setText, text)
counter['a'] += 1
time.sleep(0.1)
th = threading.Thread(target=run_thread)
th.start()
========== END EDIT ==========
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
display_update = QtCore.pyqtSignal() # ADDED
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.display_update.connect(self.guiUpdate) # ADDED
self.show()
def update(self):
while True and not self.stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
# self.guiUpdate()
self.display_update.emit() # ADDED
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update)
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
# ex.update() # - this does nothing
if __name__ == '__main__':
main()
The other thing that could be happening is deadlock from two threads trying to access the same variable. I've read that this shouldn't be possible in python, but I have experienced it from the combination of PySide and other Python C extension libraries.
May also want to join the thread on close or use the QtGui.QApplication.aboutToQuit signal to join the thread before the program closes.
The Qt documentation for QThreads provides two popular patterns for using threading. You can either subclass QThread (the old way), or you can use the Worker Model, where you create a custom QObject with your worker functions and run them in a separate QThread.
In either case, you can't directly update the GUI from the background thread, so in your update function, the guiUpdate call will most likely crash Qt if it tries to change any of the GUI elements.
The proper way to run background processes is to use one of the two QThread patterns and communicate with the main GUI thread via Signals and Slots.
Also, in the following bit of code,
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
app.exec_ starts the event loop and will block until Qt exits. Python won't run the ex.update() command until Qt has exited and the ex window has already been deleted, so you should just delete that command.
I have read a lot on threads, but I really need help with this one:
I have a PyQt Main GUI that runs an optimization with scipy.minimize...
As I cannot not make an example of this I use a "placeholder" process
to show what my problem is.
I want to let the Main GUI remain stoppable by the User, if the Process takes too long to give
a result.
My working example is this one, where I use an integration with sympy
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
import time, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class IntegrationRunner(QObject):
'Object managing the integration'
def __init__(self):
super(IntegrationRunner, self).__init__()
self._isRunning = True
def longRunning(self):
# reset
if not self._isRunning:
self._isRunning = True
print("preparing Integration")
#this is known to be time consuming and returning not the right integral
#this is a placeholder for a "time consuming" operation that sould
#be handled by a thread outside the main GUI
#imagine an optimization process or any other time consuming operation
#that would freeze the Main GUI
t=sp.symbols('t')
exp=sp.sqrt((3*t+1)/t)
sol=sp.integrate(exp,t)
print(sol)
print('finished...')
def stop(self):
self._isRunning = False
#this is displayed when the "stop button" is clicked
#but actually the integration process won´t stop
print("Integration too long - User terminated")
class SimulationUi(QDialog):
'PyQt interface'
def __init__(self):
super(SimulationUi, self).__init__()
self.goButton = QPushButton('Run Integration')
self.stopButton = QPushButton('Stop if too long')
self.layout = QHBoxLayout()
self.layout.addWidget(self.goButton)
self.layout.addWidget(self.stopButton)
self.setLayout(self.layout)
self.simulThread = QThread()
self.simulThread.start()
self.simulIntegration = IntegrationRunner()
self.simulIntegration.moveToThread(self.simulThread)
#self.simulIntegration.stepIncreased.connect(self.currentStep.setValue)
# call stop on Integr.Runner from this (main) thread on click
self.stopButton.clicked.connect(lambda: self.simulIntegration.stop())
self.goButton.clicked.connect(self.simulIntegration.longRunning)
if __name__ == '__main__':
app = QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())
After clicking the "start" button and the "stop" button before the integration stops automatically
i get this output:
>>preparing Integration
>>Integration too long - User terminated
>>Integral(sqrt((3*t + 1)/t), t)
>>finished...
This isn´t exactly my problem but I want to understand how I can use a thread where I can put
time consuming calculations and stop them in order to try other "parameters" or "initial guesses"
when using something like Scipy Minimize
Is it even possible to stop an iterative function from "outside" and restart it without getting into "non responding"?
Any help to improve here is appreciated.
I took these example here as a guideline
how-to-signal-from-a-running-qthread-back-to-...
and pastebin
I've written a GUI program which do a lot of calculation in the back, at the same time, display a progress bar. After it was done, a new screen will show the result.
Then I want to make another interface before the calculation to let the user select if they want to use the last calculation result, thus skipping the calculation.
I made a screen with a button which connected to the calculation, and a combo box to select last calculation result file.
However, when I clicked the button, it did nothing. And after around 10 secs( the duration for the calculation ), the result screen pop open. Thus, it skipped the progress bar screen. Why?
This it part of the original program:
import sys
import configparser
import getpass
import telnetlib
import time
import subprocess
from datetime import *
from log_tracker import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
class Task_Checker(QMainWindow):
def __init__(self):
super(Task_Checker, self).__init__()
config = configparser.RawConfigParser()
config.read('profile.cfg')
self.log_path = config.get('Config', 'log_path')
self.log_prefix = config.get('Config', 'log_prefix')
self.log_suffix = config.get('Config', 'log_suffix')
self.initUI()
def check_production(self):
self.log_tracker = Log_Tracker(self)
self.log_tracker.tick.connect(self.pbar.setValue)
self.log_tracker.parseConfig()
self.log_tracker.connectDb()
self.log_tracker.trackLog()
def initUI(self):
self.resize(1400, 768)
self.center()
self.statusBar().showMessage('Checking Production Programs')
self.wait_message = QLabel('Checking Production Programs')
self.wait_message.setAlignment(Qt.Alignment(Qt.AlignHCenter))
self.pbar = QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
vbox = QVBoxLayout()
vbox.addWidget(self.wait_message)
vbox.addWidget(self.pbar)
tmpWidget2 = QWidget()
tmpWidget2.setLayout(vbox)
self.setCentralWidget(tmpWidget2)
self.show()
self.check_production()
self.pbar.hide()
self.statusBar().showMessage('Processing Information')
self.tabs = QTabWidget()
mua_table = self.processInfo('MUA')
bps_table = self.processInfo('BPS')
obdua_table = self.processInfo('OBDUA')
sua_table = self.processInfo('SUA')
ngr_ftp_table = self.processInfo('NGR_FTP')
bpspdfbill_table = self.processInfo('BpsPdfBill')
disk_space_table = self.processInfo('Disk_Space')
self.tabs.addTab(mua_table, 'MUA')
self.tabs.addTab(bps_table, 'BPS')
self.tabs.addTab(obdua_table, 'ODBUA')
self.tabs.addTab(sua_table, 'SUA')
self.tabs.addTab(ngr_ftp_table, 'NGR_FTP')
self.tabs.addTab(bpspdfbill_table, 'BpsPdfBill')
self.tabs.addTab(disk_space_table, 'Disk_Space')
self.setCentralWidget(self.tabs)
self.statusBar().showMessage('Ready')
self.setWindowTitle('Task Checker')
self.show()
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def processInfo(self, project_name):
...the processing...
In order to add a new screen before the progress bar ( originally it loads the calculation right away ), I made a fews changes to initUI() and move the calculation part to a new sub routine checkProd(), then connect it with a button:
import sys
import configparser
import getpass
import telnetlib
import time
import subprocess
from datetime import *
from log_tracker import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
class Task_Checker(QMainWindow):
def __init__(self):
super(Task_Checker, self).__init__()
config = configparser.RawConfigParser()
config.read('profile.cfg')
self.log_path = config.get('Config', 'log_path')
self.log_prefix = config.get('Config', 'log_prefix')
self.log_suffix = config.get('Config', 'log_suffix')
self.initUI()
def check_production(self):
self.log_tracker = Log_Tracker(self)
self.log_tracker.tick.connect(self.pbar.setValue)
self.log_tracker.parseConfig()
self.log_tracker.connectDb()
self.log_tracker.trackLog()
def initUI(self):
self.resize(1400, 768)
self.center()
btn_check = QPushButton('Check Lastest Status', self)
btn_check.setToolTip('Click this if you want to check the lastest status in production')
combo = QComboBox()
dirlist = os.listdir(self.log_path)
for f in dirlist:
combo.addItem(f)
QtCore.QObject.connect(btn_check, QtCore.SIGNAL('clicked()'), self.checkProd)
hbox = QHBoxLayout()
hbox.addWidget(btn_check)
hbox.addWidget(combo)
tmpWidget = QWidget()
tmpWidget.setLayout(hbox)
self.setCentralWidget(tmpWidget)
self.show()
def checkProd(self):
self.statusBar().showMessage('Checking Production Programs')
self.wait_message = QLabel('Checking Production Programs')
self.wait_message.setAlignment(Qt.Alignment(Qt.AlignHCenter))
self.pbar = QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
vbox = QVBoxLayout()
vbox.addWidget(self.wait_message)
vbox.addWidget(self.pbar)
tmpWidget2 = QWidget()
tmpWidget2.setLayout(vbox)
self.setCentralWidget(tmpWidget2)
self.show()
self.check_production()
self.pbar.hide()
self.statusBar().showMessage('Processing Information')
self.tabs = QTabWidget()
mua_table = self.processInfo('MUA')
bps_table = self.processInfo('BPS')
obdua_table = self.processInfo('OBDUA')
sua_table = self.processInfo('SUA')
ngr_ftp_table = self.processInfo('NGR_FTP')
bpspdfbill_table = self.processInfo('BpsPdfBill')
disk_space_table = self.processInfo('Disk_Space')
self.tabs.addTab(mua_table, 'MUA')
self.tabs.addTab(bps_table, 'BPS')
self.tabs.addTab(obdua_table, 'ODBUA')
self.tabs.addTab(sua_table, 'SUA')
self.tabs.addTab(ngr_ftp_table, 'NGR_FTP')
self.tabs.addTab(bpspdfbill_table, 'BpsPdfBill')
self.tabs.addTab(disk_space_table, 'Disk_Space')
self.setCentralWidget(self.tabs)
self.statusBar().showMessage('Ready')
self.setWindowTitle('Task Checker')
self.show()
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def processInfo(self, project_name):
...the processing...
This is a lot of unnecessary code to read, but what I have managed to parse out of it, is that you are doing heavy calculations in the main thread, while expecting another widget in the main thread to also maintain responsiveness.
This is a common pitfall when working with PyQt4 (and probably most GUI frameworks that use a main thread event loop). The issue is that when you start your app, the main event loop constantly polls for new events to process from your GUI. Its expecting that every operation should either take a very short time to complete, or, periodically relinquish control back to the eventloop. Right now, your calculation is hogging up all the availability of the main thread, and anything that your progress window is trying to do is just backing up into a queue waiting for a chance for the event loop to pick it up.
The simple answer to this is: Do any heavy calculations in a separate QThread, and communicate back to your main thread via signals. This will require a little bit of retooling on you part. You should not put anything heavy into an init of any class. But you have managed to address this in your second example by attaching the computation to a button. Good start. The button should actually start the calc in another thread, thus not blocking up the main thread.
Qt4.7 Threading Basics
GUI Thread and Worker Thread
As mentioned, each program has one thread
when it is started. This thread is called the "main thread" (also
known as the "GUI thread" in Qt applications). The Qt GUI must run in
this thread. All widgets and several related classes, for example
QPixmap, don't work in secondary threads. A secondary thread is
commonly referred to as a "worker thread" because it is used to
offload processing work from the main thread.
...
Using Threads
There are basically two use cases for threads:
1. Make
processing faster by making use of multicore processors.
2. Keep the GUI
thread or other time critical threads responsive by offloading long
lasting processing or blocking calls to other threads.
As a quick fix if you just want to see some results, you can periodically call QtGui.QApplication.processEvents() from your calculation method. This will every so often allow the event loop to flush out pending operations. Doing so will let something like your progress widget actually function. Essentially what you are doing is manually "pumping" the event loop. This isn't the best approach though. Usually its reserved for lighter weight one off stuff. If you want the best performance, move it to a thread.