Jesvin Jose
Jesvin Jose

Reputation: 23088

How to kill a WxPython application when user clicks a frame's close

The application is supposed to close when I click the close button for the main frame. But the way I implemented it, it quits with a Segmentation fault when I click the button.

I worry about safe shutdown of the program, because I will need to persist stuff to disk later.

What is the proper non-violent way to terminate a WxPython application though a close button?


Here is the "main" loop of the program I implemented:

if __name__ == "__main__":
    app = wx.App(False)
    mf = MainFrame(None, title='Spectrum Checker') #subclasses frame
    mf.register_close_callback( app.Destroy) #what is the apt func?
    app.MainLoop()

Here is how it is implemented the callback within the MainFrame:

def __init__(self, parent, title):
    ...
    self.Bind(wx.EVT_CLOSE, self._when_closed)

...

def _when_closed(self, event):
if self.__close_callback__:
    self.__close_callback__()

Upvotes: 20

Views: 45071

Answers (4)

Kameron Kincade
Kameron Kincade

Reputation: 623

One of the previous answers discusses using Destroy() rather than Close() in order to save user information before quitting the application. Here is some sample code asking the user if they intend to quit. If they respond with an affirmative, it "destroys" the frame and quits the application.

# Bind our events from our menu item and from the close dialog 'x' on the frame
def SetupEvents(self):
    self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
    self.Bind(wx.EVT_MENU, self.OnCloseFrame, self.fileMenuExitItem)


# Destroys the main frame which quits the wxPython application
def OnExitApp(self, event):
    self.Destroy()


# Makes sure the user was intending to quit the application
def OnCloseFrame(self, event):
    dialog = wx.MessageDialog(self, message = "Are you sure you want to quit?", caption = "Caption", style = wx.YES_NO, pos = wx.DefaultPosition)
    response = dialog.ShowModal()

    if (response == wx.ID_YES):
        self.OnExitApp(event)
    else:
        event.StopPropagation()

Upvotes: 3

Mike Driscoll
Mike Driscoll

Reputation: 33071

Here's the normal method of closing a frame:

import wx

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Close Me")
        panel = wx.Panel(self)

        closeBtn = wx.Button(panel, label="Close")
        closeBtn.Bind(wx.EVT_BUTTON, self.onClose)

    #----------------------------------------------------------------------
    def onClose(self, event):
        """"""
        self.Close()

if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    frame.Show()
    app.MainLoop()

Now if you have a binding to wx.EVT_CLOSE, then you'll end up in an infinite loop until you get a segfault. The main reason to bind to EVT_CLOSE is so you can catch the close event and ask the user to save their information. If you want to do that, then you need to use self.Destroy instead of "self.Close" so you don't keep firing that event.

As has already been mentioned, in your close handler you need to make sure you end threads, destroy taskbar icons, close open files, etc so nothing hangs. See also: http://wxpython.org/docs/api/wx.CloseEvent-class.html


OP's addition

I was facing two issues, I thank all three answers for helping me find them:

First, the frame does not respond to self.Close() or self.Destroy() because it has an attribute self.stuff that has a running thread. This thread must be closed first.

Second, self.Close() in a handler that responds to close events and causes an infinite recursion when called. This causes a runtime error (segmentation fault, recursion depth exceeded). The solution is to use self.Destroy() instead.

Upvotes: 20

GP89
GP89

Reputation: 6730

Closing all the top level frames in the app should end the application without needing to bind to close or anything else. You can close them yourself with .Close() if you have several, or some that are hidden.

Also if you have a TaskBarIcon, that will need to be destroyed before the app will end.

I think your seg fault could be because of another reason, to do with how the frames tear down and destroy their children. I'd try stepping through the code and find out at what point the seg fault is happening.

Upvotes: 2

uhz
uhz

Reputation: 2518

I am not sure why do you need the callback to close the application? If you do not bind anything to the MainFrame, the application should automatically be closed when you click the close button. Unless you have some other thread running which prevents the process to end. If you still want to bind to the close event and do something before closing you should use event.Skip() in the event handler if you still want to close the window. Otherwise the event is not propagated further and default handler is not executed. An example:

import wx

class MainFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.__close_callback = None
        self.Bind(wx.EVT_CLOSE, self._when_closed)

    def register_close_callback(self, callback):
        self.__close_callback = callback

    def _when_closed(self, event):
        doClose = True if not self.__close_callback else self.__close_callback()
        if doClose:
            event.Skip()

if __name__ == "__main__":
    app = wx.App(False)
    mf = MainFrame(None, title='Test')
    mf.Show()
    mf.register_close_callback(lambda: True)
    app.MainLoop()

When you execute the code and click close - the application is closed. When you change the callback function to lambda: False the window will not be closed since Skip is not called.

Upvotes: 7

Related Questions