wxPython wx.Close create runtime error - python

When I try to call self.Close(True) in the top level Frame's EVT_CLOSE event handler, it raises a RuntimeError: maximum recursion depth exceeded. Here's the code:
from PicEvolve import PicEvolve
import wx
class PicEvolveFrame(wx.Frame):
def __init__(self, parent, id=-1,title="",pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame"):
wx.Frame.__init__(self,parent,id,title,pos,size,style,name)
self.panel = wx.ScrolledWindow(self)
self.panel.SetScrollbars(1,1,600,400)
statusBar = self.CreateStatusBar()
menuBar = wx.MenuBar()
menu1 = wx.Menu()
m = menu1.Append(wx.NewId(), "&Initialize", "Initialize population with random images")
menuBar.Append(menu1,"&Tools")
self.Bind(wx.EVT_MENU,self.OnInit,m)
self.Bind(wx.EVT_CLOSE,self.OnClose)
self.SetMenuBar(menuBar)
def OnInit(self, event):
dlg = wx.TextEntryDialog(None,"Enter Population Size:","Population Size")
popSize = 0
if dlg.ShowModal() == wx.ID_OK:
popSize = int(dlg.GetValue())
self.pEvolver = PicEvolve(popSize,(200,200),True)
box = wx.BoxSizer(wx.VERTICAL)
filenames = []
for i in range(popSize):
filenames.append("img"+str(i)+".png")
for fn in filenames:
img = wx.Image(fn,wx.BITMAP_TYPE_ANY)
box.Add(wx.StaticBitmap(self.panel,wx.ID_ANY,wx.BitmapFromImage(img)), 0,wx.BOTTOM)
self.panel.SetSizer(box)
def OnClose(self,event):
self.Close(True)
class PicEvolveApp(wx.App):
def OnInit(self):
self.frame = PicEvolveFrame(parent=None,title="PicEvolve")
self.frame.Show()
self.SetTopWindow(self.frame)
return True
if __name__ == "__main__":
app = PicEvolveApp()
app.MainLoop()

When you call window.Close it triggers EVT_CLOSE.
Quoted from http://www.wxpython.org/docs/api/wx.CloseEvent-class.html
The handler function for EVT_CLOSE is
called when the user has tried to
close a a frame or dialog box using
the window manager controls or the
system menu. It can also be invoked by
the application itself
programmatically, for example by
calling the wx.Window.Close function.
so obviously you will go into a infinite recursive loop. Instead in handler of EVT_CLOSE either destroy the window
def OnClose(self,event):
self.Destroy()
or Skip the event
def OnClose(self,event):
event.Skip(True)
or do not catch the EVT_CLOSE.
Edit:
Btw why you want to catch the event, in other question you have put some comment, you should update the question accordingly, so that people can give better answers.
e.g when your program is still waiting on command prompt after close, it may mean you have some top level window still not closed.
To debug which one is still open, try this
for w in wx.GetTopLevelWindows():
print w

You don't need to catch EVT_CLOSE unless you want to do something special, like prompt the user to save. If you do that sort of thing, then call self.Destroy() instead. Right now you call OnClose when you hit the upper right "x", which then calls "Close", which fires the OnClose event....that's why you get the recursion error.
If you don't catch EVT_CLOSE and use self.Close() it should work. When it doesn't, then that usually means you have a timer, thread or hidden top-level window somewhere that also needs to be stopped or closed. I hope that made sense.

def OnClose(self,event):
event.Skip()
see http://wiki.wxpython.org/EventPropagation

Related

wxPython Tray Icon : Exit on call from another Thread or Func

I have a wxPython TaskbarIcon class with no other Parent window, except a Tray Icon, since it runs as a sort of background process. Provided below is a complete working example -->
import subprocess as sp
import wx.adv
from threading import Thread, Event
import os, signal
####### Create Tray Icon #######
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.Append(item)
return item
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
super(TaskBarIcon, self).__init__()
self.set_icon(TRAY_ICON)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_UP, self.Run_Now)
self.Bind(wx.adv.EVT_TASKBAR_RIGHT_DCLICK, self.on_exit)
def CreatePopupMenu(self):
menu = wx.Menu()
create_menu_item(menu, 'Open Log_File', self.GoTo_Log)
menu.AppendSeparator()
create_menu_item(menu, 'Exit', self.on_exit)
return menu
def set_icon(self, path):
icon = wx.Icon(path)
self.SetIcon(icon, TRAY_TOOLTIP)
def GoTo_Log(self, event):
print("Log File will be Opened -- :)\n")
# sp.Popen(["explorer.exe", LogFile])
def Run_Now(self, event):
# Run my task
print("Running Now...")
def on_exit(self, event):
print('exiting . . . ')
wx.CallAfter(self.Destroy)
self.frame.Close(True)
self.RemoveIcon()
os.kill(pid, signal.SIGBREAK)
class App(wx.App):
def OnInit(self):
frame= wx.Frame(None)
self.SetTopWindow(frame)
sysTray= TaskBarIcon(frame)
print("Moved to Sys Tray !\n")
return True
################################
if __name__ == '__main__':
app = App(False)
app.MainLoop()
I want here to add the convenience to exit (close SysTray) on keypress (say 'F7'). I first decided to Bind EVT_KEY_DOWN with a method to detect keypress and then call on_exit. But It took me a while to realize that TaskBarIcon does not support EVT_KEY_DOWN, since there's no parent window !
Below is a my func to detect keypress ('F7') :
import keyboard, os, signal
def DetectEND(Force_END = False):
#Long Press "F7" button to Terminate this Background Application
if keyboard.is_pressed('f7') or Force_END:
print("'f7' press detected | Ending Service ...")
# cleanHandlers(B_Log) # OR some other cleaning activities before quitting
os.kill(pid, signal.SIGBREAK) # Using this instead of quit() since this would not run in the main thread !
return True
return False
Basically, I then just tried to use the above as a Thread to look up for the keypress and when returned True... wanted to communicate to the TaskBarIcon instance to call the self.on_exit method, but here too cannot figure out how to communicate without an Event binder !
Also, I am new to this wxpython and so... do not have any proper idea about the "MainLooping" of the wxpython... or else would have set up an threading.Event(), making the DetectEND func to set the Event when True, and then to check for Event.is_set() in the TaskBarIcon class, But I do know that this loop is way different from working on simple while True loops !!
Apart from the approaches mentioned above... I tried other ways too, like wx.CallAfter() from the DetectEND func itself and some global hotkey method, but None did work in this case or sometimes I could not figure out the implementation.
THANK YOU -- for staying with me till this end !
At last would appreciate any method that gets the job done ... and a Windows-specific method would even be too Great.
Some other methods just thought of by me -- like one that does not involve threading (or) a way to bind a Key_down event to TaskBarIcon (or) a workaround for the CallAfter to call the self.on_exit method from another non-class function... (does not matter)

Trying to create a dialog in another thread wxpython

I'm running a function in another thread that is supposed to fill out a dialog and then show it but it just seg faults as soon as I tried to alter the dialog in any way. I've read that this is a common issue with WxPython and that devs are not intended to directly alter dialogs in another thread.
How do I get around this? I can just call the function in my main thread but that will block my GUI and it is a lengthy operation to initialize the dialog - I would like to avoid this.
My code is similar to the below.
In the main thread
# Create the dialog and initialize it
thread.start_new_thread(self.init_dialog, (arg, arg, arg...))
The function I am calling
def init_dialog(self, arg, arg, arg....):
dialog = MyFrame(self, "Dialog")
# Setup the dialog
# ....
dialog.Show()
Even with a blank dialog and just a simple call to show inside the function I get a segmentation fault. Any help would be greatly appreciated, thanks.
I have made an applet to demonstrate keeping GUI responsive during calculations and calling the message box after the calculations.
import wx
import threading
import time
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "I am a test frame")
self.clickbtn = wx.Button(self, label="click me!")
self.Bind(wx.EVT_BUTTON, self.onClick)
def onClick(self, event):
self.clickbtn.Destroy()
self.status = wx.TextCtrl(self)
self.status.SetLabel("0")
print "GUI will be responsive during simulated calculations..."
thread = threading.Thread(target=self.runCalculation)
thread.start()
def runCalculation(self):
print "you can type in the GUI box during calculations"
for s in "1", "2", "3", "...":
time.sleep(1)
wx.CallAfter(self.status.AppendText, s)
wx.CallAfter(self.allDone)
def allDone(self):
self.status.SetLabel("all done")
dlg = wx.MessageDialog(self,
"This message shown only after calculation!",
"",
wx.OK)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_OK:
self.Destroy()
mySandbox = wx.App()
myFrame = TestFrame()
myFrame.Show()
mySandbox.MainLoop()
GUI stuff is kept in the main thread, while calculations continue unhindered. The results of the calculation are available at time of dialog creation, as you required.

Global event handlers in wxPython (Phoenix)

I'm having a problem trying to handle focus events in wxPython 3, the phoenix fork.
I'm trying to clear the value in a TextCtrl when it's focused if it still has its default value, and then restore that default value on leaving focus if the user hasn't entered a different value.
This is the handler I have:
def on_enter(self, event):
control = wx.Window.FindFocus()
if control.Id in TEXT_MAPPING.keys():
if control.Value == TEXT_MAPPING[control.Id]:
control.SetValue('')
exit_control = event.GetWindow()
if exit_control.Id in (TEXT_MAPPING.keys()):
if not exit_control.GetValue():
exit_control.SetValue(TEXT_MAPPING[exit_control.Id])
event.Skip()
The handler itself works fine, though it's a little clunky. My issue is that I would like to bind this on a global level, such that whenever any wx.EVT_SET_FOCUS event is fired, I can have it be handled by this function.
I found this:
import wx
class MyApp(wx.App):
def FilterEvent(self, evt):
print evt
return -1
app = MyApp(redirect=False)
app.SetCallFilterEvent(True)
frm = wx.Frame(None, title='Hello')
frm.Show()
app.MainLoop()
but I'm confused on how I would pass the event down to the children of MyApp.
What you can do is bind focus kill and focus select to functions, like this:
self.user_text.Bind(wx.EVT_SET_FOCUS, self.on_focus_username)
self.user_text.Bind(wx.EVT_KILL_FOCUS, self.on_deselect_username)
where self is the panel where the TextCtrl "user_text" is located".
The functions might look like this:
def on_focus_username(self, event):
if self.user_text.GetForegroundColour() != wx.BLACK:
self.user_text.SetForegroundColour(wx.BLACK)
# Clear text when clicked/focused
self.user_text.Clear()
def on_deselect_username(self, event):
if self.user_text.GetLineLength(0) is 0:
self.user_text.SetForegroundColour(wx.Colour(192, 192, 192))
# Put a default message when unclicked/focused away
self.user_text.SetValue("Enter Username")
Hope this helped, if I didn't answer something clearly/correctly let me know.

wx.EVT_MAXIMIZE prevented exiting full screen

I have a frame with one radio box to toggle full screen. The frame is to go full screen when the user clicks the Maximize button. However, if I use the maximize button, the radio box would then fail to restore the window. If I use the radio box to go full screen, it will be able to restore the window.
import wx
class FSWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((800, 600))
self.RadioFullScreen = wx.RadioBox(self, -1, "Display", choices=["Windowed","Full Screen"])
self.RadioFullScreen.Bind(wx.EVT_RADIOBOX, self.FS)
self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
self.Sizer = None
self.Show()
def FS(self, Event):
if self.RadioFullScreen.GetSelection():
self.ShowFullScreen(True)
else:
self.ShowFullScreen(False)
def OnMaximize(self, Event):
self.ShowFullScreen(True) # <-- Add self.Restore() or self.Maximize(False) here
self.RadioFullScreen.SetSelection(1)
App = wx.App()
frame =FSWindow(None, -1, "MainWindow")
App.MainLoop()
However, if I add self.Restore() or self.Maximize(False) before the self.ShowFullScreen(True) like I commented on the source code above, the radio buttons will work. Problem is, the window will be restored first before going full screen which is ugly. Any solution for this? Also please explain why this happened, if possible.
Running Python 2.7.9, WxPython 3.0.2 on Window 7 Professional 32-bit
It seems that ShowFullScreen is not setting some flag, so things get out of sync.
If I just use Maximize/Restore things work fine for me, i.e. following changes to your code.
def FS(self, Event):
if self.RadioFullScreen.GetSelection():
self.Maximize()
#self.ShowFullScreen(True, style=wx.FULLSCREEN_ALL)
print('done fs true')
else:
#self.ShowFullScreen(False, style=wx.FULLSCREEN_ALL)
self.Restore()
print('done fs false')
def OnMaximize(self, Event):
Event.Skip()
self.RadioFullScreen.SetSelection(1)
print('done max')
If you don't want the menu bar etc when the screen is maximized then uncomment the ShowFullScreen lines.
You are handling an event "Maximize", most of the time you want default behaviour also to happen, that is why I added Event.Skip to the OnMaximize handler - in this case it doesn't make a difference as it looks like the event is only fired after maximisation is already done.

How do I bind wx.CLOSE_BOX to a function?

Fairly simple question, but I can't seem to find the answer. I have a GUI which has a cancel button that asks the user to abort all unsaved changes when they press it. The GUI also has a wx.CLOSE_BOX, but this simply closes it because its not bound to my OnCancel function. How do I bind it?
Things I tried:
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum), wx.CLOSE_BOX)
#This gives an AssertionError, replacing wx.EVT_CLOSE with wx.EVT_BUTTON also
# gives the same error
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum))
#This binds any time ```self.Close(True)``` occurs (which makes sense) but
# is not what I want. There are other buttons which close the GUI which should not
# use the OnCancel function
Thanks in advance for your help
EDIT: The code below should help clarify what I'm looking for
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
newCRNum = 0
cancelBtn = wx.Button(self, -1, "Cancel")
self.Bind(wx.EVT_BUTTON, lambda event: self.OnCancel(event, newCRNum), cancelBtn)
def OnCancel(self, event, CRNum):
dlg = wx.MessageDialog(self, "Are you sure you want to cancel? All work will be lost and CR number will not be reserved.", "Cancel CR", wx.YES_NO|wx.NO_DEFAULT|wx.ICON_EXCLAMATION)
if dlg.ShowModal() == wx.ID_YES:
self.Destroy()
else:
dlg.Destroy
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
So what this does is create a whimsically large cancel button. When this button is pressed, a dialog box pops up and prompts the user if they really want to quit. If they say yes, the whole gui closes, if not, only the dialog box closes.
When the user presses the red (X) button in the top right of the GUI, I want the same thing to happen. Since is a button, I assume it can be bound to my OnCancel button, but how do I do this?
Reason for AssertionError: The third argument must be source widget like follow.
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum), self)
Try following example:
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
newCRNum = 0
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum))
def OnCancel(self, event, num):
dlg = wx.MessageDialog(self, 'Do you want close?', 'Sure?',
wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_OK:
event.Skip()
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()

Categories