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()
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()
My problem is as follows:
I am designing a wizard for the construction of an object to be added to a list of objects in the calling frame of my program. At the end of the wizard I would like to pass the newly created object back to the calling frame to be inserted into the list. In order to simulate this basic functionality on an abstract basis I have constructed the following, scaled down app:
mainframe.py
import wx
import wiz_test
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,title="Main")
panel = wx.Panel(self)
callButton = wx.Button(panel, label = "Call Wizard")
callButton.Bind(wx.EVT_BUTTON,self.launchWizard)
self.Show()
def launchWizard(self,event):
wiz = wiz_test.WizObj(self)
a = 0
if wiz == wx.wizard.EVT_WIZARD_FINISHED:
a = wiz.answer
print a
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
wiz_test.py
import wx
import wx.wizard as wiz
class WizPage(wiz.WizardPageSimple):
def __init__(self, parent):
self.answer = 3
wiz.WizardPageSimple.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
title = wx.StaticText(self, -1, "Wizard Page")
title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
class WizObj(object):
def __init__(self,parent):
wizard = wx.wizard.Wizard(None, -1, "Simple Wizard")
page1 = WizPage(wizard)
wizard.FitToPage(page1)
wizard.RunWizard(page1)
wizard.Destroy()
if __name__ == "__main__":
app = wx.App(False)
main()
app.MainLoop()
The ultimate goal in this small example is to get the MainFrame instance to output the value '3' derived from the .answer member variable of the WizObj instance when the wx.wizard.EVT_WIZARD_FINISHED event is triggered. However it is clearly not working at this point as the current code only returns '0'. Am I approaching this the correct way? Should I be binding the EVT_WIZARD_FINISHED event instead, and if so, how would I access that from Mainframe?
I was able to solve this problem through the use of the "pubsub" capability within the wxPython library. Specifically, I added a pub.subscribe() instance immediately prior to the instantiation of my wizard within the calling frame. Inside of the wizard I pass the value via pub.sendMessage() just before destroying the wizard. It is important to note that the pass value had to be specified in order for the pubsub send would work effectively.
The following code is the modified version of the original code which now functions.
MainFrame.py
import wx
import wiz_test
from wx.lib.pubsub import pub
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,title="Main")
panel = wx.Panel(self)
callButton = wx.Button(panel, label = "Call Wizard")
callButton.Bind(wx.EVT_BUTTON,self.launchWizard)
self.Show()
def catch_stuff(self,a):
print a
def launchWizard(self,event):
pub.subscribe(self.catch_stuff,'valPass')
wiz = wiz_test.WizObj(self,a)
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
wiz_test.py
import wx
import wx.wizard as wiz
from wx.lib.pubsub import pub
class WizPage(wiz.WizardPageSimple):
def __init__(self, parent):
self.answer = 3
wiz.WizardPageSimple.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
title = wx.StaticText(self, -1, "Wizard Page")
title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
#----------------------------------------------------------------------
class WizObj(object):
def __init__(self,parent,a):
wizard = wx.wizard.Wizard(None, -1, "Simple Wizard")
page1 = WizPage(wizard)
wizard.FitToPage(page1)
wizard.RunWizard(page1)
pub.sendMessage('valPass',a = page1.answer)
wizard.Destroy()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
main()
app.MainLoop()
The result is that the console prints the value 3 which was retrieved from the called wizard.
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 want a script in python which opens a logger window when the test is running and the output I want to see will be pushed to the logger window. I found baisc GUI script using wxpython but I dont know how to push my output to the gui being opened. Can anyone help me?
My Code:
import wx
import thread
import threading
class ExamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.quote = wx.StaticText(self, label="Your Log :", pos=(10, 10))
self.logger = wx.TextCtrl(self, pos=(0,40), size=(1100,1100), style=wx.TE_MULTILINE | wx.TE_READONLY)
def append_txt(self,txt):
self.logger.AppendText(txt)
def sample_Window():
app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
panel.append_txt("Log Starts Here\n First line of code \n Second line of code ")
app.MainLoop()
sample_Window()
Once I give the app.Mainloop I am not able give further input to the appendtext method. I got suggestions to use threading to run the append_txt as a separate thread to pass the argument but i am not sure how to do it. My goal is call a method and pass on the text as argument which will show the text in logger window.
The simpliest way to do it is to embed your task into the panel, and start it with a thread:
import wx
import thread
import threading
import time
class ExamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.quote = wx.StaticText(self, label="Your Log :", pos=(10, 10))
self.logger = wx.TextCtrl(self, pos=(0,40), size=(1100,1100), style=wx.TE_MULTILINE | wx.TE_READONLY)
########################################################################
# Use a thread to start your task
########################################################################
task_thread = threading.Thread(target = self.my_task, args = ())
task_thread.setDaemon(True)
task_thread.start()
def append_txt(self,txt):
self.logger.AppendText(txt)
def my_task(self):
########################################################################
# Do your job right here and update log
########################################################################
for i in range(100):
self.append_txt('\nNew line added(No.%s)' % (i + 1))
time.sleep(1)
def sample_Window():
app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
panel.append_txt("Log Starts Here\n First line of code \n Second line of code ")
app.MainLoop()
sample_Window()
Update:
Well, here's another way to do it:
import wx
import thread
import threading
import time
class ExamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.quote = wx.StaticText(self, label="Your Log :", pos=(10, 10))
self.logger = wx.TextCtrl(self, pos=(0,40), size=(1100,1100), style=wx.TE_MULTILINE | wx.TE_READONLY)
def append_txt(self,txt):
self.logger.AppendText(txt)
def sample_Window():
app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
panel.append_txt("Log Starts Here\n First line of code \n Second line of code ")
############################################################################
# Use thread to update logs
############################################################################
task_thread = threading.Thread(target=my_task, args=(panel, ))
task_thread.setDaemon(True)
task_thread.start()
app.MainLoop()
def my_task(panel):
############################################################################
# Do your job right here and update log
############################################################################
for i in range(100):
panel.append_txt('\nNew line added(No.%s)' % (i + 1))
time.sleep(1)
sample_Window()
I am working with python v2.7 and wxPython v3.0 on Windows 7 OS. In my application I have a panel named as myPanel. I have a image as a background on myPanel the image names is green.bmp The myPanel contains a button named as myButton. This myButton also contains an image as a background named as blue.bmp The thread simply changes the image on myButton.
For the demo purpose I am using the same image again and again on myButton. In my real world problem I have different images.
Problem: After executing my application, when I see the memory consumption in the task manager I observed that the memory consumption keeps increasing. What is wrong with my code below that causes unnecessary memory consumption? How can I avoid this?
Code: The images used in the code can be downloaded from here Green.bmp and Blue.bmp. The code snippet is provided below:
import wx
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
from threading import Thread
import threading
import time
class gui(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, None, id, title, size=(500,400))
myPanel = wx.Panel(self, -1, size=(300,200))
image_file1 = 'green.bmp'
image1 = wx.Image(image_file1, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.bitmap2 = wx.StaticBitmap(myPanel, -1, image1, (0, 0))
pub.subscribe(self.addImage, 'Update')
def addImage(self):
myButton = wx.Button(self.bitmap2, -1, size =(30,30), pos=(20,20))
image_file2 = 'blue.bmp'
image2 = wx.Image(image_file2, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.bitmap1 = wx.StaticBitmap(myButton, -1, image2, (0, 0))
class myThread(Thread):
def __init__(self):
Thread.__init__(self)
self.start()
def run(self):
while True:
time.sleep(2)
wx.CallAfter(pub.sendMessage, 'Update')
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
myThread()
app.MainLoop()
Thanks for your time.
Your problem is in addImage. Each time this line executes:
myButton = wx.Button(self.bitmap2, -1, size =(30,30), pos=(20,20))
You are adding another child window to self.bitmap2. The window perfectly overlaps the previous one added, so it is not apparent that you have multiple children. To see what is happening, add this line to the bottom of addImage:
print len(self.bitmap2.GetChildren())
To fix this problem you should destroy all the children before you add the new button. Add this to the top of addImage
self.bitmap2.DestroyChildren()
However, this approach destroys ALL windows you have added to bitmap2, so tread lightly. If you want to only destroy the button, you should keep a reference to it. I've modified your program to use this approach:
import wx
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
from threading import Thread
import threading
import time
class gui(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, None, id, title, size=(500,400))
myPanel = wx.Panel(self, -1, size=(300,200))
image1 = wx.Image('green.bmp', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.bitmap2 = wx.StaticBitmap(myPanel, -1, image1, (0, 0))
pub.subscribe(self.addImage, 'Update')
self.myButton = None
def addImage(self):
# Don't keep adding children to bitmap2
if self.myButton:
self.myButton.Destroy()
self.myButton = wx.Button(self.bitmap2, -1, size =(30,30), pos=(20,20))
image2 = wx.Image('blue.bmp', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.bitmap1 = wx.StaticBitmap(self.myButton, -1, image2, (0, 0))
class myThread(Thread):
def __init__(self):
Thread.__init__(self)
self.start()
def run(self):
while True:
time.sleep(2)
wx.CallAfter(pub.sendMessage, 'Update')
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
myThread()
app.MainLoop()