I am trying to create a window that receives simple event notifications from a Process. Here is the code that I have so far:
import wx, wx.lib.newevent, time, sys
from multiprocessing import Process
size_width = 320
size_height = 240
background_color = (226,223,206)
SomeNewEvent, EVT_SOME_NEW_EVENT = wx.lib.newevent.NewEvent()
class StatusWindow(wx.Frame):
def __init__(self, parent):
super(StatusWindow, self).__init__(parent, title='Monitor', size=(size_width, size_height))
self.Bind(EVT_SOME_NEW_EVENT, self.updateStatus)
staticBox = wx.StaticBox(self, label='Monitor Status:', pos=(5, 105), size=(size_width - 28, size_height/3))
self.statusLabel = wx.StaticText(staticBox, label='None', pos=(10, 35), size=(size_width, 20), style=wx.ALIGN_LEFT)
self.count = 0
self.InitUI()
self.monitor = cMonitor()
self.monitor.start()
def InitUI(self):
panel = wx.Panel(self)
self.SetBackgroundColour(background_color)
self.Centre()
self.Show()
def updateStatus(self, evt):
self.statusLabel.SetLabel(evt.attr1)
class cMonitor(Process):
def __init__(self):
super(cMonitor, self).__init__()
def run(self):
time.sleep(2)
print 'This is an update'
#create the event
evt = SomeNewEvent(attr1="Some event has just occured")
#post the event
wx.PostEvent(EVT_SOME_NEW_EVENT, evt)
if __name__ == '__main__':
app = wx.App()
window = StatusWindow(None)
app.MainLoop()
The window gets created, but the Process does not appear to be either executing or sending the post event notification correctly. I should note that the print statement in the run method is not showing up either. What is causing the GUI to not be updated?? This was what I used as a reference:
http://wiki.wxpython.org/CustomEventClasses
First of all, your code throws an error:
Traceback (most recent call last):
File "C:\Python27\lib\multiprocessing\process.py", line 232, in _bootstrap
self.run()
File "C:\PyProgs\stackoverflow_answers\wx_answers\wx_events1.py", line 44, in run
wx.PostEvent(EVT_SOME_NEW_EVENT, evt)
File "C:\Python27\lib\site-packages\wx-3.0-msw\wx\_core.py", line 8410, in PostEvent
return _core_.PostEvent(*args, **kwargs)
TypeError: in method 'PostEvent', expected argument 1 of type 'wxEvtHandler *'
According the docs, PostEvent(dest, event) send an event to a window or other wx.EvtHandler to be processed later, but in your code first parameter has type PyEventBinder. Your code would have to look something like this:
wx.PostEvent(self.wxWindow, evt)
where self.wxWindow - object of StatusWindow class. But there is another problem: you can not use wxPython objects as multiprocessor arguments(link).
One way to do what you want - using threading module instead multiprocessing:
import wx, wx.lib.newevent, time, sys
from threading import *
size_width = 320
size_height = 240
background_color = (226,223,206)
SomeNewEvent, EVT_SOME_NEW_EVENT = wx.lib.newevent.NewEvent()
class StatusWindow(wx.Frame):
def __init__(self, parent):
super(StatusWindow, self).__init__(parent, title='Monitor', size=(size_width, size_height))
self.Bind(EVT_SOME_NEW_EVENT, self.updateStatus)
staticBox = wx.StaticBox(self, label='Monitor Status:', pos=(5, 105), size=(size_width - 28, size_height/3))
self.statusLabel = wx.StaticText(staticBox, label='None', pos=(10, 35), size=(size_width, 20), style=wx.ALIGN_LEFT)
self.count = 0
self.InitUI()
# Set up event handler for any worker thread results
self.monitor = cMonitor(self)
self.monitor.start()
def InitUI(self):
panel = wx.Panel(self)
self.SetBackgroundColour(background_color)
self.Centre()
self.Show()
def updateStatus(self, evt):
self.statusLabel.SetLabel(evt.attr1)
class cMonitor(Thread):
def __init__(self, wxWindow):
super(cMonitor, self).__init__()
self.wxWindow = wxWindow
def run(self):
time.sleep(2)
print 'This is an update'
#create the event
evt = SomeNewEvent(attr1="Some event has just occured")
#post the event
wx.PostEvent(self.wxWindow, evt)
if __name__ == '__main__':
app = wx.App()
window = StatusWindow(None)
app.MainLoop()
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 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()
Python 2.7, WxPython 3.0.2
We are trying to automatically close an entire program under certain conditions. For various reasons, we can't just kill the process. We've had some level of success with it. We can close it if there's no modal dialogs, or a single modal dialog. Once we introduce the second modal dialog (nested), it fails to stop properly.
The actual error received appears to be:
wx._core.PyAssertionError: C++ assertion "IsRunning()" failed at ..\..\src\common\evtloopcmn.cpp(83) in wxEventLoopBase::Exit(): Use ScheduleExit() on not running loop
Here's a working example of our issue. The frame will automatically close after 5 seconds. Clicking the button will load a dialog. Clicking the button on the dialog will open another dialog. It works fine until the last dialog is opened.
from threading import Thread
from time import sleep
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
self.Show()
self.__someDialog = None
self.__myThread = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
self.__myThread.setDaemon(True)
self.__myThread.start()
def __onOK(self, evt):
self.__someDialog = SomeDialog(self)
self.__someDialog.ShowModal()
def closeOpenDialogs(self):
lst = wx.GetTopLevelWindows()
for i in range(len(lst) - 1, 0, -1):
if isinstance(lst[i], wx.Dialog):
print "Closing " + str(lst[i])
lst[i].Close(True)
#lst[i].Destroy()
def __waitThenClose(self):
for x in range(0, 5):
print "Sleeping..."
sleep(1)
self.closeOpenDialogs()
wx.CallAfter(self.Close, True)
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
self.SetSize((300, 300))
self.__anotherDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __onOK(self, evt):
self.__anotherDialog = AnotherDialog(self)
self.__anotherDialog.ShowModal()
def __on_btn_cancel(self, event):
self.EndModal(wx.ID_CANCEL)
class AnotherDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, None, id=-1, title='Another Dialog')
self.SetSize((200, 200))
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __on_btn_cancel(self, event):
self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.App()
mainFrame = MainFrame()
app.MainLoop()
I think what is happening here is that the first call to ShowModal() blocks the at the app level (not just the frame level) which is preventing the second dialog from becoming fully initialized. To work around this issue I would call Show() instead of ShowModal() and add wx.FRAME_FLOAT_ON_PARENT to the dialog style flags. You can also call Disable() on the parts of the program you don't want the user to interact with while the dialogs are open.
EDIT: Here is a working example:
from threading import Thread
from time import sleep
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
self.Show()
self.__someDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
self.__myThread.setDaemon(True)
self.__myThread.start()
def __onOK(self, evt):
self.__someDialog = SomeDialog(self)
self.__someDialog.ShowModal()
def closeOpenDialogs(self, evt=None):
lst = wx.GetTopLevelWindows()
for i in range(len(lst) - 1, 0, -1):
dialog = lst[i]
if isinstance(dialog, wx.Dialog):
print "Closing " + str(dialog)
# dialog.Close(True)
wx.CallAfter(dialog.Close)
# sleep(1)
# dialog.Destroy()
def __waitThenClose(self):
for x in range(0, 10):
print "Sleeping..."
sleep(1)
wx.CallAfter(self.closeOpenDialogs)
wx.CallAfter(self.Close, True)
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
self.SetSize((300, 300))
self.__anotherDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __onOK(self, evt):
self.__anotherDialog = AnotherDialog(self)
self.__anotherDialog.SetWindowStyleFlag(
wx.FRAME_FLOAT_ON_PARENT|wx.DEFAULT_DIALOG_STYLE)
self.__anotherDialog.Show()
def __on_btn_cancel(self, event):
event.Skip()
self.EndModal(wx.ID_CANCEL)
class AnotherDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Another Dialog')
self.SetSize((200, 200))
wx.EVT_CLOSE(self, self.__on_btn_cancel)
parent.Disable()
def __on_btn_cancel(self, event):
event.Skip()
self.GetParent().Enable()
# self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.App()
mainFrame = MainFrame()
app.MainLoop()
The only way to reliably gracefully close all the modal dialogs, whether they were explicitly opened by your own code or not, is to use wxModalDialogHook to remember all the opened dialogs and then close them all, in the reverse (i.e. LIFO) order, before quitting the application.
Unfortunately I don't know if wxModalDialogHook is available in Python.
I am working on a GUI app using python v2.7 and wxPython v3.0 on windows 7 OS.
I have to update my GUI continuously which contains lots of panels. Each panels contains a wx.StaticText. I have to update these wx.StaticTexts continuously. I thought of using threads. Also I am using pubsub module for communicating with the GUI to update these wx.StaticTexts. Every thing works as intended.
I have created a short demo below of my real problem.
Problem: In my code below, two threads are created. Both the threads are able to update the GUI using wx.CallAfter(). What if I have 100 panels to update? Do I need to create 100 classes for each of the thread which updates a particular panel? I want the threads to work independently of the other threads.
What will possibly be the better approach than this one?
Code: Please find the sample code below to play around:
import wx
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
import time
from threading import Thread
import threading
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 500
screenHeight = 400
screenSize = (screenWidth,screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.locationFont = locationFont = wx.Font(12, wx.MODERN, wx.NORMAL, wx.BOLD)
mainSizer = wx.BoxSizer(wx.VERTICAL)
myPanelA = wx.Panel(self, style=wx.SIMPLE_BORDER)
myPanelA.SetBackgroundColour('#C0FAE0')
self.myTextA = wx.StaticText(myPanelA, -1, "I have a problem :( ")
myPanelB = wx.Panel(self, style=wx.SIMPLE_BORDER)
myPanelB.SetBackgroundColour('#C0FAFF')
self.myTextB = wx.StaticText(myPanelB, -1, "Me too :( ")
mainSizer.Add(myPanelA, 1, wx.EXPAND, 5)
mainSizer.Add(myPanelB, 1, wx.EXPAND, 5)
self.SetSizer(mainSizer)
pub.subscribe(self.updatePanelA, 'Update-panelA')
pub.subscribe(self.updatePanelB, 'Update-panelB')
def updatePanelA(self, message):
self.myTextA.SetLabel(message)
def updatePanelB(self, message):
self.myTextB.SetLabel(message)
class threadA(Thread):
def __init__(self):
Thread.__init__(self)
self.start()
def run(self):
ObjA = updateGUI()
ObjA.methodA()
class threadB(Thread):
def __init__(self):
Thread.__init__(self)
self.start()
def run(self):
ObjB = updateGUI()
ObjB.methodB()
class updateGUI():
def methodA(self):
while True:
time.sleep(3)
wx.CallAfter(pub.sendMessage, 'Update-panelA', message='Problem solved')
def methodB(self):
while True:
time.sleep(5)
wx.CallAfter(pub.sendMessage, 'Update-panelB', message='Mine too')
if __name__=='__main__':
app = wx.App()
frame = GUI(parent=None, id=-1, title="Problem Demo")
frame.Show()
threadA()
threadB()
app.MainLoop()
Thank you for your time!
You can define your private "selfUpdatePanel" to launch its own thread to update its own text field. The code would be easy maintain in this way.
Check following code modified based on your code:
import wx
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
import time
from threading import Thread
import threading
class selfUpdatePanel(wx.Panel):
def __init__(self, parent, mystyle, interval, topic, message):
wx.Panel.__init__(self, parent, style = mystyle)
pub.subscribe(self.updatePanel, topic)
self.updateMsg = message
self.textCtrl = None
self.interval = interval
self.topic = topic
pub.subscribe(self.updatePanel, self.topic)
def setTextCtrl(self,text):
self.textCtrl = text
def updatePanel(self):
self.textCtrl.SetLabel(self.updateMsg)
def threadMethod(self):
while True:
print "threadMethod"
time.sleep(self.interval)
wx.CallAfter(pub.sendMessage, self.topic)
def startThread(self):
self.thread = Thread(target=self.threadMethod)
self.thread.start()
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 500
screenHeight = 400
screenSize = (screenWidth,screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.locationFont = locationFont = wx.Font(12, wx.MODERN, wx.NORMAL, wx.BOLD)
mainSizer = wx.BoxSizer(wx.VERTICAL)
#myPanelA = wx.Panel(self, style=wx.SIMPLE_BORDER)
myPanelA = selfUpdatePanel(self, wx.SIMPLE_BORDER, 3, 'Update-panelA', 'Problem solved')
myPanelA.SetBackgroundColour('#C0FAE0')
self.myTextA = wx.StaticText(myPanelA, -1, "I have a problem :( ")
myPanelA.setTextCtrl(self.myTextA)
#myPanelB = wx.Panel(self, style=wx.SIMPLE_BORDER)
myPanelB = selfUpdatePanel(self, wx.SIMPLE_BORDER, 5, 'Update-panelB', 'Mine too')
myPanelB.SetBackgroundColour('#C0FAFF')
self.myTextB = wx.StaticText(myPanelB, -1, "Me too :( ")
myPanelB.setTextCtrl(self.myTextB)
mainSizer.Add(myPanelA, 1, wx.EXPAND, 5)
mainSizer.Add(myPanelB, 1, wx.EXPAND, 5)
self.SetSizer(mainSizer)
myPanelB.startThread()
myPanelA.startThread()
if __name__=='__main__':
app = wx.App()
frame = GUI(parent=None, id=-1, title="Problem Demo")
frame.Show()
app.MainLoop()
I can't understand why I am loosing control of my GUI even though I am implementing a thread to play a .wav file. Can someone pin point what is incorrect?
#!/usr/bin/env python
import wx, pyaudio, wave, easygui, thread, time, os, sys, traceback, threading
import wx.lib.delayedresult as inbg
isPaused = False
isStopped = False
class Frame(wx.Frame):
def __init__(self):
print 'Frame'
wx.Frame.__init__(self, parent=None, id=-1, title="Jasmine", size=(720, 300))
#initialize panel
panel = wx.Panel(self, -1)
#initialize grid bag
sizer = wx.GridBagSizer(hgap=20, vgap=20)
#initialize buttons
exitButton = wx.Button(panel, wx.ID_ANY, "Exit")
pauseButton = wx.Button(panel, wx.ID_ANY, 'Pause')
prevButton = wx.Button(panel, wx.ID_ANY, 'Prev')
nextButton = wx.Button(panel, wx.ID_ANY, 'Next')
stopButton = wx.Button(panel, wx.ID_ANY, 'Stop')
#add widgets to sizer
sizer.Add(pauseButton, pos=(1,10))
sizer.Add(prevButton, pos=(1,11))
sizer.Add(nextButton, pos=(1,12))
sizer.Add(stopButton, pos=(1,13))
sizer.Add(exitButton, pos=(5,13))
#initialize song time gauge
#timeGauge = wx.Gauge(panel, 20)
#sizer.Add(timeGauge, pos=(3,10), span=(0, 0))
#initialize menuFile widget
menuFile = wx.Menu()
menuFile.Append(0, "L&oad")
menuFile.Append(1, "E&xit")
menuBar = wx.MenuBar()
menuBar.Append(menuFile, "&File")
menuAbout = wx.Menu()
menuAbout.Append(2, "A&bout...")
menuAbout.AppendSeparator()
menuBar.Append(menuAbout, "Help")
self.SetMenuBar(menuBar)
self.CreateStatusBar()
self.SetStatusText("Welcome to Jasime!")
#place sizer on panel
panel.SetSizer(sizer)
#initialize icon
self.cd_image = wx.Image('cd_icon.png', wx.BITMAP_TYPE_PNG)
self.temp = self.cd_image.ConvertToBitmap()
self.size = self.temp.GetWidth(), self.temp.GetHeight()
wx.StaticBitmap(parent=panel, bitmap=self.temp)
#set binding
self.Bind(wx.EVT_BUTTON, self.OnQuit, id=exitButton.GetId())
self.Bind(wx.EVT_BUTTON, self.pause, id=pauseButton.GetId())
self.Bind(wx.EVT_BUTTON, self.stop, id=stopButton.GetId())
self.Bind(wx.EVT_MENU, self.loadFile, id=0)
self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
self.Bind(wx.EVT_MENU, self.OnAbout, id=2)
#Load file using FileDialog, and create a thread for user control while running the file
def loadFile(self, event):
foo = wx.FileDialog(self, message="Open a .wav file...", defaultDir=os.getcwd(), defaultFile="", style=wx.FD_MULTIPLE)
foo.ShowModal()
self.queue = foo.GetPaths()
self.threadID = 1
while len(self.queue) != 0:
self.song = myThread(self.threadID, self.queue[0])
self.song.start()
while self.song.isAlive():
time.sleep(2)
self.queue.pop(0)
self.threadID += 1
def OnQuit(self, event):
self.Close()
def OnAbout(self, event):
wx.MessageBox("This is a great cup of tea.", "About Jasmine", wx.OK | wx.ICON_INFORMATION, self)
def pause(self, event):
global isPaused
isPaused = not isPaused
def stop(self, event):
global isStopped
isStopped = not isStopped
class myThread (threading.Thread):
def __init__(self, threadID, wf):
self.threadID = threadID
self.wf = wf
threading.Thread.__init__(self)
def run(self):
global isPaused
global isStopped
self.waveFile = wave.open(self.wf, 'rb')
#initialize stream
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format = self.p.get_format_from_width(self.waveFile.getsampwidth()), channels = self.waveFile.getnchannels(), rate = self.waveFile.getframerate(), output = True)
self.data = self.waveFile.readframes(1024)
isPaused = False
isStopped = False
#main play loop, with pause event checking
while self.data != '':
# while isPaused != True:
# if isStopped == False:
self.stream.write(self.data)
self.data = self.waveFile.readframes(1024)
# elif isStopped == True:
# self.stream.close()
# self.p.terminate()
self.stream.close()
self.p.terminate()
class App(wx.App):
def OnInit(self):
self.frame = Frame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
def main():
app = App()
app.MainLoop()
if __name__=='__main__':
main()
Your loadFile method, quite independently of the fact that it delegates song-playing to many threads (which it waits for in strange ways, but, that's another issue), is still monopolizing the wx event loop until it returns. Where you currently have a time.sleep, try adding app.Yield(True) (of course you need to make app visible at that point in your code: simplest though inelegant is to add a global app at the start of main.
Event-driven systems typically serve the event loop only when your various event handler methods return: if you have a long-running event handler, you need to explicitly yield control to the event loop once in a while. Various event systems offer different ways to do it: in wx, it's the Yield method which I just recommended you try. See the brief description un the docs.