How do I bind wx.CLOSE_BOX to a function? - python

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

Related

Disabled button still catching clicks during long tasks wxpython

Disabled button still catch clicks during the long task. During the long tasks the button is grayed out but if you click it during the long task, click event fires after the long task has finished. e.g.
def onClick(self, evt):
self.btn.Disable()
for i in range (1000):
print i
self.btn.Enable()
Button disables itself before executing the long for loop, but if we click the button during for loop, it starts the for loop again, because it calls the onClick function again, after the for loop finishes.
Any idea how to disable the click event as well ?
Although I have my doubts as to whether you should be coding your long running event this way, you can achieve what you want by using Unbind on the button click, perform the long running task, using Yield to use up any subsequent button clicks and then at the end of the task Bind to the button again.
i.e.
import wx
import time
class ButtonFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.btn = wx.Button(self, -1, "Click Me")
self.btn.Bind(wx.EVT_BUTTON, self.onClick)
self.Centre()
self.Show()
def onClick(self, event):
self.btn.Unbind(wx.EVT_BUTTON)
for i in range (10):
time.sleep(1)
print( i )
wx.GetApp().Yield() # Yielding allows button events to be used up
self.btn.Bind(wx.EVT_BUTTON, self.onClick)
print ("Accepting clicks again")
if __name__ == "__main__":
app = wx.App()
ButtonFrame()
app.MainLoop()
To be honest I didn't really get what you are asking.
Your code works as follows:
When you click on the button, the button (i.e. self.btn) is disabled
It will stayed disabled and execute the for loop
Once done executing the for loop, the button goes back to live
If you would like to disable the button, you should do it outside of the onclick event.
For example:
self.btn.Disable() # This will grey out the button, you can't click it, so the following onClick function wouldn't be triggered
def onClick(self, evt):
# do something
If you would like to use the button to trigger a task execution, and disable the button that triggers the task when the task is in the middle of execution, the best way is to use multi-thread. You can take a look at the following two links for more information:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
https://wiki.wxpython.org/LongRunningTasks
In fact it's easier than my first answer suggested. There is no reason to UnBind, simply using Yield before re-enabling the button will suffice:
import wx
import time
class ButtonFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,-1,"Disable Button Events")
panel = wx.Panel(self, -1)
self.btn = wx.Button(panel, -1, "Click Me", pos=(10,10), size=(80,30))
self.btn.Bind(wx.EVT_BUTTON, self.onClick)
self.Show()
def onClick(self, event):
self.btn.Disable()
for i in range (10):
time.sleep(1)
print("Long task running",i)
wx.GetApp().Yield() # Yielding allows button events to be used up
self.btn.Enable()
print("Accepting clicks again")
if __name__ == "__main__":
app = wx.App()
ButtonFrame()
app.MainLoop()

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.

wxpython unittest with modal dialog

I have a wxPython GUI, and I am attempting to use unittest to test some of my modal dialogs. I tried to follow the example given here (you have to scroll down to the bottom of the page): http://wiki.wxpython.org/Unit%20Testing%20with%20wxPython, but it does not work for me. It simply freezes in the middle.
I've adapted the code from the wiki to this:
btn_id = wx.NewId()
class MyDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1, 'Test')
self.btn = wx.Button(self, btn_id, label="OK!!")
self.btn.Bind(wx.EVT_BUTTON, self.close_dialog)
def close_dialog(self, event):
print 'close me'
class TestMyDialog(unittest.TestCase):
def setUp(self):
self.app = wx.App()
self.frame = wx.Frame(None)
self.frame.Show()
def tearDown(self):
wx.CallAfter(self.app.Exit)
self.app.MainLoop()
def testDialog(self):
def clickOK():
clickEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, btn_id)
self.dlg.ProcessEvent(clickEvent)
print 'finished clickOK'
wx.CallAfter(clickOK)
self.ShowDialog()
def ShowDialog(self):
self.dlg = MyDialog(self.frame)
self.dlg.ShowModal()
self.dlg.Destroy()
if __name__ == '__main__':
unittest.main()
To my understanding, what should happen is that ShowDialog is called, then gets 'stuck' on ShowModal, at which time clickOk should run (called by wx.CallAfter). This seems to happen, but for some reason the click event isn't actually processed, and the tests hangs. When I run MyDialog not in testing the event binding works fine and the dialog closes when the Ok button is clicked.
I shouldn't need app.mainloop() to be able to ProcessEvent, right? What is going on here?
Have a look at the unittests in Phoenix https://github.com/wxWidgets/Phoenix , look at test_dialog.py and the base staff in wtc.py

showing another window/frame in wxPython

I am fairly new to programming and to python and wxpython. I have looked over this code for literally HOURS and I tried finding an answer everywhere online. I am having trouble getting a new window to show up after a menu item is clicked. Here is my code so far...
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
panel=wx.Panel(self)
wx.Frame.CenterOnScreen(self)
##wx.Frame.Maximize(self)
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
edit_menu=wx.Menu()
ID_FILE_NEW = 1
ID_FILE_OPEN = 2
ID_EDIT_UNDO = 3
ID_EDIT_REDO = 4
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
file_menu.Append(ID_FILE_OPEN,"Open...","This will open a new window")
edit_menu.Append(ID_EDIT_UNDO,"Undo","This will undo your last action")
edit_menu.Append(ID_EDIT_REDO,"Redo","This will redo your last undo")
menubar.Append(file_menu,"File")
menubar.Append(edit_menu,"Edit")
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, NewWindow.new_frame, None, 1)
class NewWindow(wx.Frame):
def __init__(self,MainWindow,id):
wx.Frame.__init__(self, None, id, 'New Window', size=(600,400))
wx.Frame.CenterOnScreen(self)
self.Show(False)
def new_frame(self, event):
NewWindow.Show(True)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()
When I try to run this code, I get this error message once I click on the menu item "New Window"
TypeError: unbound method new_frame() must be called with NewWindow instance as first argument (got CommandEvent instance instead)
Again, I am fairly new to programming. Any help is greatly appreciated and also, I know my code may not be the "cleanest" looking code around. Thanks in advance!
You don't seem to understand how classes work in Python. You try to call NewWindow.new_frame, but you never actually create an instance of that class.
The error message is because you are calling the method on the class instead of on an instance of the class. What you want to do is something like:
newWin = NewWindow(...) # replace ... with the appropriate parameters
newWin.Show(True)
You don't provide enough information in your example to know what the appropriate parameters are for the NewWindow call (e.g., you don't show where you create the main window), but the MainWindow and id parameters in NewWindow.__init__ aren't just there for looks: wxPython needs to know the parent window. You should look into the wxPython documentation to understand how to create a wxFrame.
Modifying your code to some extent i was able to show a new window when user clicks a New Window option,
Do check the stuff that i have modified a let me know if this is what you want??
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
panel=wx.Panel(self)
wx.Frame.CenterOnScreen(self)
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
edit_menu=wx.Menu()
ID_FILE_NEW = 1
ID_FILE_OPEN = 2
ID_EDIT_UNDO = 3
ID_EDIT_REDO = 4
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
file_menu.Append(ID_FILE_OPEN,"Open...","This will open a new window")
edit_menu.Append(ID_EDIT_UNDO,"Undo","This will undo your last action")
edit_menu.Append(ID_EDIT_REDO,"Redo","This will redo your last undo")
menubar.Append(file_menu,"File")
menubar.Append(edit_menu,"Edit")
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.test, None, 1)
def test(self, event):
self.new = NewWindow(parent=None, id=-1)
self.new.Show()
class NewWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self, parent, id, 'New Window', size=(400,300))
wx.Frame.CenterOnScreen(self)
#self.new.Show(False)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()

wxPython wx.Close create runtime error

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

Categories