How to Properly Handle Multiple Calls to wx.App() using wxPython Phoenix - python

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()

Related

wxPython application does not respond

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()

How do I stop a thread when its done working and restart it when a button is pressed?

I have an installer I am creating for a game and as of now there are two buttons. One downloads the game, and one starts the game if it detects the executable. I multi-threaded both buttons so that my GUI will not freeze when I click either button. The problem is, if I click one of the buttons, the other will not work until restarting the application. I need some way for the thread to close after its process is completed so that the thread is open for the other button to work.
Here is what I have so far:
# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
# Define global variables
url = "{ENTER DROPBOX URL HERE}" # The url to the file we are downloading
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # Bind specific events to event handlers
ID_START = wx.NewId()# Button definitions
EVT_RESULT_ID = wx.NewId()# Define notification event for thread completion
# Version Check
def VersionCheck():
try:
CurrentVersion = os.listdir("./RFMB6_WINDOWS/")[0] # Checks the version currently downloaded
VersionCheck = requests.get('https://pastebin.com/raw/yc30uwAh') # Checks the newest version
NewestVersion = VersionCheck.text # Converts VersionCheck to a string
if CurrentVersion == NewestVersion:
message = 'It looks like you have the newest version already.\n Are you sure you want to download?'
wx.MessageBox(message=message, caption='RFMP GUIntaller | Complete!', style=wx.OK | wx.ICON_INFORMATION)
else:
print('\n\nThere is an update available, would you like to install it?')
pass
except:
print("It looks like you don't have RFMP installed yet. Let me fix that for you.")
# Downloads new file
def Download():
urllib.request.urlretrieve(url, 'RFMP.zip')
# Extracts new file
def Extract():
zip_ref = zipfile.ZipFile("RFMP.zip", 'r')
zip_ref.extractall("RFMB6_WINDOWS")
zip_ref.close()
# Deletes the .zip file but leave the folder
def Clean():
os.remove("RFMP.zip")
class ProgressEvent(wx.PyCommandEvent):
"""Event to signal that a status or progress changed"""
def __init__(self, etype, eid, status=None, progress=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)
self._status = status # field to update label
self._progress = progress # field to update progress bar
def GetValue(self):
"""Returns the value from the event.
#return: the tuple of status and progress
"""
return (self._status, self._progress)
# Thread class that executes processing
class DLThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
# This is the code executing in the new thread.
self.sendEvent('Checking for old files...', 00)
self.sendEvent('Checking for old files...', 100)
time.sleep(.5)
if os.path.exists("RFMB6_WINDOWS"):
self.sendEvent('Removing old files...', 200)
subprocess.check_call(('attrib -R ' + 'RFMB6_WINDOWS' + '\\* /S').split())
shutil.rmtree('RFMB6_WINDOWS')
time.sleep(.3)
self.sendEvent('Removed old files.', 300)
else:
time.sleep(.3)
self.sendEvent('No old files found.', 300)
time.sleep(.3)
pass
self.sendEvent('Downloading Package...', 400)
Download()
self.sendEvent('Downloading complete.', 600)
time.sleep(.3)
self.sendEvent('Extracting...', 650)
Extract()
self.sendEvent('Extraction complete.', 900)
time.sleep(.3)
self.sendEvent('Cleaning up...', 950)
Clean()
time.sleep(.3)
self.sendEvent('Cleaning complete.', 1000)
time.sleep(.5)
done = ("Installation the RFMP Private Alpha has been completed!")
wx.MessageBox(message=done, caption='RFMP GUIntaller | Complete!', style=wx.OK | wx.ICON_INFORMATION)
self._notify_window.worker = None
def sendEvent(self, status=None, progress=None):
# Send event to main frame, first param (str) is for label, second (int) for the progress bar
evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
wx.PostEvent(self._notify_window, evt)
class StartAppThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
try:
subprocess.run('RFMB6_WINDOWS/RFMB6_WINDOWS/RFMB6.exe')
except:
error = ("Failed to locate RFMB6.exe. Please don't move any game files after downloading.")
wx.MessageBox(message=error, caption='RFMP GUIntaller | Error!',
style=wx.OK | wx.ICON_ERROR)
self._notify_window.worker = None
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
^ wx.MAXIMIZE_BOX)
self.SetSize(400, 350)
self.Centre()
DLStart = wx.Button(self.bitmap1, ID_START, 'Download RFMP', size=(175,50), pos=(50,260))
DLStart.Bind(wx.EVT_BUTTON, self.OnButton_DLStart)
AppStart = wx.Button(self.bitmap1, ID_START, 'Start RFMP', size=(175,50), pos=(50,160))
AppStart.Bind(wx.EVT_BUTTON, self.OnButton_AppStart)
self.status = wx.StaticText(self.bitmap1, -1, '', pos=(10,215), style=wx.NO_BORDER)
self.status.SetBackgroundColour((255,255,0)) # set text back color
self.gauge = wx.Gauge(self.bitmap1, range = 1000, size = (375, 30), pos=(10,230),
style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
self.Bind(EVT_PROGRESS, self.OnResult) # Bind the custom event to a function
def OnButton_DLStart(self, event):
# Trigger the worker thread unless it's already busy
VersionCheck()
if not self.worker:
self.worker = DLThread(self)
def OnButton_AppStart(self, event):
if not self.worker:
self.worker = StartAppThread(self)
def OnResult(self, event):
"""Our handler for our custom progress event."""
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
Your issue is caused by the fact that self.worker has a value.
You need to reset self.worker.
Below I have adjusted your code to do that and in doing so I have renamed notify_window to parent, simply because it makes what is going on more obvious and fits with python standards. I'm sure that there are many others ways of achieving this, this is just a simplistic way of achieving it, in this case.
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
class DLThread(Thread):
"""Worker Thread Class."""
def __init__(self, parent):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.parent = parent
self.stop_download = 0
self.setDaemon(1)
self.start()
def run(self):
# This is the code executing in the new thread.
'''
This is what runs on a separate thread when you click the download button
'''
x = 0
while self.stop_download == 0:
time.sleep(0.5)
x +=1
if x > 20:
self.stop_download = 1
print ("Downloading App", x)
print("Download finished")
self.parent.worker = None
def stop(self):
self.stop_download = 1
print ("Download Cancelled")
class StartAppThread(Thread):
"""Worker Thread Class."""
def __init__(self, parent):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.parent = parent
self.stop_app_thread = 0
self.setDaemon(1)
self.start()
def run(self):
# This is the code executing in the new thread.
'''
This is what runs on a separate thread when you click the Start App button.
'''
x= 0
while self.stop_app_thread == 0:
print ("Game in progress",str(x))
time.sleep(0.5)
x +=1
print ("Game finished")
self.parent.worker = None
def stop(self):
self.stop_app_thread = 1
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
#Main Window
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
^ wx.MAXIMIZE_BOX)
self.SetSize(400, 350)
#self.bitmap1 = wx.StaticBitmap(self)
self.bitmap1 = wx.Panel(self)
self.Centre()
# Variables
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # Bind specific events to event handlers
ID_START = wx.NewId()# Button definitions
EVT_RESULT_ID = wx.NewId()# Define notification event for thread completion
# Download button
DLStart = wx.Button(self.bitmap1, ID_START, 'Download', size=(175,50), pos=(50,260))
DLStart.Bind(wx.EVT_BUTTON, self.OnButton_DLStart)
# App Start button
AppStart = wx.Button(self.bitmap1, ID_START, 'Start App', size=(75,50), pos=(50,160))
AppStart.Bind(wx.EVT_BUTTON, self.OnButton_AppStart)
# App Stop button
AppStop = wx.Button(self.bitmap1, ID_START, 'Stop', size=(75,50), pos=(150,160))
AppStop.Bind(wx.EVT_BUTTON, self.OnButton_AppStop)
# Progress bar
self.gauge = wx.Gauge(self.bitmap1, range = 1000, size = (375, 30), pos=(10,230), style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
self.Bind(EVT_PROGRESS, self.OnResult) # Bind the custom event to a function
def OnButton_DLStart(self, event):
# Trigger the worker thread unless it's already busy
if not self.worker:
self.worker = DLThread(self)
def OnButton_AppStart(self, event):
if not self.worker:
self.worker = StartAppThread(self)
def OnButton_AppStop(self, event):
if self.worker:
self.worker.stop()
print ("App Stop command")
def OnResult(self, event):
"""Our handler for our custom progress event."""
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()

wxPython: Show Frame while doing something in other thread

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!)"

Threading in wx.lib.editor

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

Opening a wx.Frame in Python via a new thread

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()

Categories