wxPython fails to quit
Question: My wxPython GUI either quits with a Segmentation Fault or fails to quit at all
using the standard options. The only successful quit option (no errors) is
wx.Exit, which I understand is not a great practice. I've traced the issues
down to a few factors, but I'm scratching my head as to why they are having
this effect.

Using the wxPython inspector (wx.lib.inspection.InspectionTool()), I've been
able to determine that a FigureFrameWxAgg is being created when I run certain
pylab functions (pylab.xticks() is the function that creates it here, but I
haven't tracked down every single function that has this effect). I don't know
what this window is for. It's invisible and doesn't appear to do anything.
However, this window totally messes up the shutdown of my GUI. If I use
self.Destroy, Python doesn't shut down fully. If I use sys.exit, I get a
Segmentation fault. I need to catch the wx.EVT_CLOSE so that I can prompt the
user to save his/her work.

Here is the code for a simplified version of the GUI:

    
    
    import matplotlib
    matplotlib.use('WXAgg')
    from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanvas
    import wx
    import wx.grid
    import wx.lib.scrolledpanel
    import wx.lib.inspection
    import sys
    import pylab
    
    class my_frame(wx.Frame):
    
        def __init__(self):
            wx.Frame.__init__(self, None, -1, 'Many Rows')
            self.InitUI()
    
        def InitUI(self):
            self.panel = wx.Window(self, size=(200, 200))
            hbox_all= wx.BoxSizer(wx.HORIZONTAL)
            self.create_menubar()
    
            self.fig1 = pylab.Figure((5, 5), dpi=100)
            self.canvas1 = FigCanvas(self.panel, -1, self.fig1)
            self.fig1.text(0.01,0.98,"Arai plot",{'family':'Arial', 'fontsize':10, 'style':'normal','va':'center', 'ha':'left' })
            self.araiplot = self.fig1.add_axes([0.1,0.1,0.8,0.8])
            self.araiplot.clear()
            self.araiplot.plot(range(5),range(5),lw=0.75,clip_on=False)
            xt = pylab.xticks()
    
            grid = wx.grid.Grid(self.panel)
            grid.ClearGrid()
            grid.CreateGrid(100, 100)
            grid.AutoSize()
    
            hbox_all.AddSpacer(self.canvas1)
            hbox_all.AddSpacer(20)
            hbox_all.AddSpacer(grid)
            hbox_all.AddSpacer(20)
    
            self.panel.SetSizer(hbox_all)
            hbox_all.Fit(self)
            self.Centre()
            self.Show()
    
        def create_menubar(self):
            """                                                                                                                           
            Create menu bar                                                                                                               
            """
            self.menubar = wx.MenuBar()
            menu_file = wx.Menu()
            menu_file.AppendSeparator()
            m_exit = menu_file.Append(wx.ID_EXIT, "Quit", "Quit application")
            self.Bind(wx.EVT_CLOSE, self.on_menu_exit)
            self.menubar.Append(menu_file, "&File")
            self.SetMenuBar(self.menubar)
    
    
        def on_menu_exit(self, event):
            self.Destroy() # this doesn't quit Python fully, unless I comment out 'matplotlib.use('WXAgg')' 
            #for w in wx.GetTopLevelWindows():
            #    if w.Title == 'Figure 1':
            #        w.Destroy()  # if I pre-destroy the FigureFrameWxAgg window, I get a PyDeadObjectError when I run self.Destroy
            # self.Destroy() #                              
            # wx.Exit() # forces the program to exit, with no clean up.  works, but not an ideal solution                             
            #sys.exit() # program closes, but with segmentation error                                                                 
            #self.Close()  # creates infinite recursion error, because we have a binding to wx.EVT_CLOSE  
    
    
    if __name__ == '__main__':
        app = wx.PySimpleApp(redirect=False)                                     
        app.frame = my_frame()
        if '-i' in sys.argv:
            wx.lib.inspection.InspectionTool().Show()
        app.MainLoop()
    

To add one more level of complexity, the Segmentation Fault with sys.exit()
only happens with my brew installed Python. Sys.exit() works fine with Canopy
Python.

My questions are: how can I fix this error? And, is using wx.Exit() really so
bad?


Answer: There are several issues with your example:

  1. Do not use `pylab` in GUI applications, because is brings its own mainloop (which will not quit when the wxPython mainloop quits). You had to kill pylab.
    
        # both not required
    # matplotlib.use('WXAgg')
    # import pylab
    # use instead
    from matplotlib.figure import Figure
    ...
    def __init__(# ...
        ...
        self.fig1 = Figure((5, 5), dpi=100)
    

  2. Your menu item "Close" does not work (at least not on Windows). `wx.ID_EXIT` is meant for buttons in dialogs. Do not ask me which predefined IDs are meant for menus.
    
            ID_QUIT = wx.NewId()
        menu_file.Append(ID_QUIT , "Quit", "Quit application")
        # 
        self.Bind(wx.EVT_MENU, self.on_quit, id=ID_QUIT)
    
    def on_quit(self, evt):
        self.Close()
    

  3. In this case it is not necessary to bind to `wx.EVT_CLOSE`. If you want to do something on the close event, you have to skip it. When you skip it, wxPython will deal with it on its own.
    
            self.Bind(wx.EVT_CLOSE, self.on_close)
        ...
    
    def on_close(self, evt):
        # you can veto the close here or perform cleanup
        evt.Skip() 
    

If you change your code accordingly, wxPython will close everything properly.

