in my GUI with wxPython if have to do some calculations which can take some time. So I want to start them in a seperate Thread and show a window in the GUI that prints that the program is calculating. The main windows should be disabled during this.
So that's my code:
import time
import threading
import wx
def calculate():
# Simulates the calculation
time.sleep(5)
return True
class CalcFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent=None, id=-1, title="Calculate")
# Normally here are some intctrls, but i dont show them to keep it easy
self.panel = wx.Panel(parent=self, id=-1)
self.createButtons()
def createButtons(self):
button = wx.Button(parent=self.panel, id=-1, label="Calculate")
button.Bind(wx.EVT_BUTTON, self.onCalculate)
def onCalculate(self, event):
calcThread = threading.Thread(target=calculate)
checkThread = threading.Thread(target=self.checkThread, args=(calcThread,))
self.createWaitingFrame()
self.waitingFrame.Show(True)
self.Disable()
calcThread.run()
checkThread.run()
def createWaitingFrame(self):
self.waitingFrame = wx.MiniFrame(parent=self, title="Please wait")
panel = wx.Panel(parent=self.waitingFrame)
waitingText = wx.StaticText(parent=panel, label="Please wait - Calculating", style=wx.ALIGN_CENTER)
def checkThread(self, thread):
while thread.is_alive():
pass
print "Fertig"
self.waitingFrame.Destroy()
self.Enable()
app = wx.PySimpleApp()
frame = CalcFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
But my problem is now, that if i press the "Calculate" button, the waitingFrame isnt't shown right, I can't see the text. I also cant move/maximize/minimize the main window.
Can you help me? Thank you in advance :)
you should never update the gui in a thread other than the main thread .... Im pretty sure the docs for wxPython mention this in several places .. it is not thread safe and your gui gets in broken states ...
instead I believe you are supposed to do something like
def thread1():
time.sleep(5)
return True
def thread2(t1,gui):
while thread.is_alive():
pass
print "Fertig"
wx.CallAfter(gui.ThreadDone)
class MyFrame(wx.Frame):
def startThread(self):
calcThread = threading.Thread(target=thread1)
checkThread = threading.Thread(target=thread2, args=(calcThread,self))
def ThreadDone(self):
print "Both threads done???"
print "Now modify gui from main thread(here!)"
Related
I have a complex application, with a GUI that needs to dialogue with some I/O devices and with some WebAPI. I put my wx.Frame class in the main file, as I read that the GUI should be in the main thread to avoid freezing
if __name__ == "__main__":
app = wx.App()
frame = Window()
app.MainLoop()
but still the GUI freezes very often, and sometimes it doesn't show at all and a message saying "My_app is not responding" appears.
All the I/O and webAPI management is done in separate threads that are created by frame. The only GUI elements that are not in the main file are the pages that compose my notebook
from PageOne import PageOne
from PageTwo import PageTwo
from PageThree import PageThree
...
self.page1 = PageOne(self.nb)
self.page2 = PageTwo(self.nb)
self.page3 = PageThree(self.nb)
self.nb.AddPage(self.page1, "Page1")
self.nb.AddPage(self.page2, "Page2")
self.nb.AddPage(self.page3, "Page3")
All the communications between secondary threads and the GUI are done using wx.lib.newevent.NewEvent() in the main file and wx.PostEvent(self.parent, my_evt) in the threads.
I am using wxpython 4.1.1 and Ubuntu 20.04.2 LTS.
Any suggestion on how to prevent the GUI from not responding or freezing? Is maybe a better idea to use multiprocessing instead of multithreading? I know that threads are usually better for I/O applications...but is it still true in my case where the threads are all enless loops?
def run(self):
while True:
do_stuff()
This is not an answer per se.
However it might help track down a solution.
Given that we have no idea, none, what your code is doing, no one can answer a question like this.
It's the equivalent of asking, How long is a piece of string?
The best advice is to use something like the code below, adapting the thread to perform something akin to what your thread/threads are doing and see if you can replicate the behaviour and hopefully find the cause.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.btn = wx.Button(panel,label='Stop Long running process', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(240,10), pos=(10,50), range=240)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.time = time.time()
self.start() # start the thread
def run(self):
# A loop that will run for 2 minutes then terminate
while self.stopthread == False:
curr_loop = int(time.time() - self.time)
if curr_loop < 240:
time.sleep(0.1)
evt = progress_event(count=curr_loop)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.thread_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
def Thread_Frame(self, event):
self.thread_count += 1
frame = ThreadFrame(title='Threaded Task '+str(self.thread_count), parent=self.parent)
def AddText(self,event):
self.text_count += 1
txt = "Gui is still active " + str(self.text_count)+"\n"
self.txt.write(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
I am trying to troubleshoot/de-bug an issue I came across with my application using wxPython 4.0.7.
I re-wrote my entire program that was functioning with Python 2.7 and wxPython 2.8 on a Windows 7 32-bit system to now work with 64 bit Python 3.7.4 and wxPython 4.0.7 on a 64 bit Windows 10 system.
The problem I am having is that my program requires that it iterate multiple times based on the number of loops specified by the user, and it calls an instance of wx.App() from two different python scripts utilized.
I have read that calling multiple instances of wx.App() is a "no-no" (see creating multiple instances of wx.App)
Clearly this is a problem with this version of wxPython as my application crashes after the first iteration now, when it worked fine before.
Okay, so I understand this now, but I am not certain what the "fix" is for my particular issue.
The basic outline of my application is this:
A "runner.py" script is launched which contains the main wx.frame() gui and the following code is appended to the end of the script:
app = wx.App()
frame = Runner(parent=None, foo=Foo)
frame.Show()
app.MainLoop()
When the user clicks on the "execute" button in the wxPython GUI, I have a progress dialog that initiates using this code:
pd = wx.ProgressDialog(title = "Runner.py", message= "Starting Model", parent=self, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)
The runner.py script executes a "for loop" that does a bunch of stuff (actually reads in some inputs from R scripts) and then once it's done, it opens up a second python script ("looping.py") and iterates through a set of processes based on the number of loops the user specifies in the GUI launched from runner.py.
As the user needs to visually see what loop process the model run is going through, I have inside this second "looping.py" script, yet another instance of wx.App() that calls up another wx.ProgressDialog(), And the script looks like this:
#Progress Bar to user to start model
app = wx.App()
pd = wx.ProgressDialog("looping.py", "Setup Iteration", parent=None, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)
My specific question is this: How do I initiate the wx.ProgressDialog() successfully within the "looping.py" script without it crashing my application past the first iteration?
You will probably have to sub-class wx.ProgressDialog, arguably it may be easier to write your own progress bar display.
Something like this, may give you some ideas.
I've included the ability to run multiple threads doing different things, with pause and stop buttons. The main frame has a button to test whether the Gui is still active, whilst running the threads.
Updates from the thread are driven by an event
You may wish to reduce or increase its options.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(400,400))
panel = MyPanel(self)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
self.btn_start = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(180,30), pos=(10,10))
btn_test = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(180,30), pos=(10,50))
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(300,100))
self.btn_start.Bind(wx.EVT_BUTTON, self.Start_Process)
btn_test.Bind(wx.EVT_BUTTON, self.ActiveText)
def Start_Process(self, event):
process1 = ProcessingFrame(title='Threaded Task 1', parent=self, job_no=1)
process2 = ProcessingFrame(title='Threaded Task 2', parent=self, job_no=2)
self.btn_start.Enable(False)
def ActiveText(self,event):
self.text_count += 1
txt = "Gui is still active " + str(self.text_count)+"\n"
self.txt.write(txt)
class ProcessingFrame(wx.Frame):
def __init__(self, title, parent=None,job_no=1):
wx.Frame.__init__(self, parent=parent, title=title, size=(400,400))
panel = wx.Panel(self)
self.parent = parent
self.job_no = job_no
self.btn = wx.Button(panel,label='Stop processing', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.btn_pause = wx.Button(panel,label='Pause processing', size=(200,30), pos=(10,50))
self.btn_pause.Bind(wx.EVT_BUTTON, self.OnPause)
self.progress = wx.Gauge(panel,size=(200,10), pos=(10,90), range=60)
self.process = wx.TextCtrl(panel,size = (200,250), pos=(10,120), style = wx.TE_MULTILINE)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
def OnProgress(self, event):
self.progress.SetValue(event.count)
self.process.write(event.process+"\n")
self.Refresh()
if event.count >= 60:
self.OnExit(None)
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.parent.btn_start.Enable(True)
self.Destroy()
def OnPause(self, event):
if self.mythread.isAlive():
self.mythread.pause() # Pause the thread
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.target = parent_target
self.stopthread = False
self.process = 1 # Testing only - mock process id
self.start() # start the thread
def run(self):
# A selectable test loop that will run for 60 loops then terminate
if self.target.job_no == 1:
self.run1()
else:
self.run2()
def run1(self):
curr_loop = 0
while self.stopthread != True:
if self.stopthread == "Pause":
time.sleep(1)
continue
curr_loop += 1
self.process += 10 # Testing only - mock process id
if curr_loop <= 60: # Update progress bar
time.sleep(1.0)
evt = progress_event(count=curr_loop,process="Envoking process "+str(self.process))
#Send back current count for the progress bar
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.terminate()
self.terminate()
def run2(self):
curr_loop = 0
while self.stopthread != True:
if self.stopthread == "Pause":
time.sleep(1)
continue
curr_loop += 1
self.process += 100 # Testing only - mock process id
if curr_loop <= 60: # Update progress bar
time.sleep(1.0)
evt = progress_event(count=curr_loop,process="Checking process"+str(self.process))
#Send back current count for the progress bar
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.terminate()
self.terminate()
def terminate(self):
self.stopthread = True
def pause(self):
if self.stopthread == "Pause":
self.stopthread = False
self.target.btn_pause.SetLabel('Pause processing')
else:
self.stopthread = "Pause"
self.target.btn_pause.SetLabel('Continue processing')
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
I have a problem with PyQT4 for Python. There is a label with text and button connected to function. The function could change the text of label first, then call other function. There is a problem with it: the function is executed firts, then change text of the label.
Code:
# -*- coding: utf-8 -*-
import time
import sys
from PyQt4 import QtCore, QtGui
def timesleep():
print("start sleep")
time.sleep(5)
print("stop sleep")
class AnyWidget(QtGui.QWidget):
def __init__(self,*args):
QtGui.QWidget.__init__(self,*args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome",frame) # Текстовая метка.
global glabel
glabel = label
gridlay.addWidget(label,0,0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1,1,0)
boxlay.addWidget(frame)
def ts(self):
global glabel
glabel.setText(u"Waiting...")
timesleep()
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Help me please to fix this problem.
It work's like that because rendering is done later in app. So your glabel.text is changed immediately but you will see changed text on screen after your ts function call, because drawing is done at the end of loop.
If you really want to call your function in new frame (after rendering of new text) then use timer:
timer = QtCore.QTimer()
QtCore.QObject.connect(
timer,
QtCore.SIGNAL("timeout()"),
self.ts
)
timer.start(10)
It should call your function ten milisecond later, so in fact probably after rendering.
You never want to tie-up your GUI with a long-running function. That the label does not update is only one manifestation of the problem. Even if you get the label to update before the function is called, it will still "freeze" your GUI -- no widget will respond to the user until the long-running function completes.
If you have to run such a function, see if there is a way to break it up into small pieces which each relinquish control to the GUI, or consider using a separate thread to run the function:
import time
import sys
from PyQt4 import QtCore, QtGui
class TimeSleep(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.parent = parent
def run(self):
print("start sleep")
time.sleep(5)
print("stop sleep")
self.parent.glabel.setText(u"Done")
class AnyWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(
frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome", frame) # Текстовая метка.
self.glabel = label
gridlay.addWidget(label, 0, 0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1, 1, 0)
boxlay.addWidget(frame)
def ts(self):
self.glabel.setText(u"Waiting...")
self.thread = TimeSleep(parent=self)
self.thread.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Here is a wiki page on how to deal with long-running functions.
The text of the label is being changed, but because you're blocking the main thread you're not giving Qt a chance to paint it.
Use QCoreApplication::processEvents and QCoreApplication::flush:
def ts(self):
glabel.setText(u"Waiting...")
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.flush()
timesleep()
I have a problem with use thread and editor control, I can't use thread process in editor.
For example, I just simple add text into editor control with thread, but it happen bug:
PyAssertionError: C++ assertion "m_buffer && m_buffer->IsOk()" failed at ..\..\include\wx/dcbuffer.h(104) in wxBufferedDC::UnMask(): invalid backing store
Here is my code:
import threading
import wx
import wx.lib.editor as editor
class RunTest(threading.Thread):
def __init__(self,master,type):
threading.Thread.__init__(self)
self.master = master
self.type = type
def run(self):
if self.type == 1:
for i in range(1000):
self.master.ed.BreakLine(None)
self.master.ed.SingleLineInsert("Text Line Number: " + str(i))
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Test', size=(900, 700))
win = wx.Panel(self, -1)
self.ed = editor.Editor(win, -1)
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.ed, 1, wx.ALL|wx.GROW, 1)
win.SetSizer(box)
win.SetAutoLayout(True)
self.ed.Bind(wx.EVT_LEFT_DCLICK, self.OnClick)
def OnClick(self,event):
thread = RunTest(self,1)
thread.start()
if __name__ == '__main__':
app = wx.PySimpleApp()
root = Test()
root.Show()
app.MainLoop()
Please help me fix it. I use wx.python library,python 2.7, run in window 7
You usually get this kind of errors, when you try to update GUI widgets in non GUI thread.
I have made a small library for those purposes: https://github.com/vaal12/workerthread
Specifically look to #workerthread.executeInGUIThreadDecorator, example in examples\example_gui_class.py
I have a frame that exists as a start up screen for the user to make a selection before the main program starts. After the user makes a selection I need the screen to stay up as a sort of splash screen until the main program finishes loading in back.
I've done this by creating an application and starting a thread:
class App(wx.App):
'''
Creates the main frame and displays it
Returns true if successful
'''
def OnInit(self):
try:
'''
Initialization
'''
self.newFile = False
self.fileName = ""
self.splashThread = Splash.SplashThread(logging, self)
self.splashThread.start()
#...More to the class
which launches a frame:
class SplashThread(threading.Thread):
def __init__(self, logger, app):
threading.Thread.__init__(self)
self.logger = logger
self.app = app
def run(self):
frame = Frame(self.logger, self.app)
frame.Show()
The app value is needed as it contains the callback which allows the main program to continue when the user makes their selection. The problem is that the startup screen only flashes for a millisecond then goes away, not allowing the user to make a selection and blocking the rest of start up.
Any ideas? Thanks in advance!
You don't need threads for this. The drawback is that the splash window will block while loading but that is an issue only if you want to update it's contents (animate it) or if you want to be able to drag it. An issue that can be solved by periodically calling wx.SafeYield for example.
import time
import wx
class Loader(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
self.btn1 = wx.Button(self, label="Option 1")
self.btn2 = wx.Button(self, label="Option 2")
sizer.Add(self.btn1, flag=wx.EXPAND)
sizer.Add(self.btn2, flag=wx.EXPAND)
self.btn1.Bind(wx.EVT_BUTTON, self.OnOption1)
self.btn2.Bind(
wx.EVT_BUTTON, lambda e: wx.MessageBox("There is no option 2")
)
def OnOption1(self, event):
self.btn1.Hide()
self.btn2.Hide()
self.Sizer.Add(
wx.StaticText(self, label="Loading Option 1..."),
1, wx.ALL | wx.EXPAND, 15
)
self.Layout()
self.Update()
AppFrame(self).Show()
class AppFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
time.sleep(3)
parent.Hide()
# the top window (Loader) is hidden so the app needs to be told to exit
# when this window is closed
self.Bind(wx.EVT_CLOSE, lambda e: wx.GetApp().ExitMainLoop())
app = wx.PySimpleApp()
app.TopWindow = Loader()
app.TopWindow.Show()
app.MainLoop()