1

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?

J Jones
  • 3,060
  • 4
  • 26
  • 43

2 Answers2

4

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.

nepix32
  • 3,012
  • 2
  • 14
  • 29
  • In response to 1: My gui has extensive functionality that uses pylab. Do you have a recommendation on how to maintain this functionality without using pylab, and without rewriting all of the plots? Can I use pyplot, or does that also have its own mainloop? In response to 2: Looking around, I find a lot of tutorials etc. recommending the use of wx.ID_EXIT in menus. Can you provide any additional explanation of why this is a bad idea? – J Jones Jan 23 '15 at 19:28
  • @JaneJones: to use matplotlib with wxpython you need to "embed" the figure in a wxPython window that you manage. Search with terms like "matplotlib embedding wxpython". Here's an example: http://stackoverflow.com/questions/10737459/embedding-a-matplotlib-figure-inside-a-wxpython-panel – tom10 Jan 23 '15 at 23:50
  • @Jane Jones: Regarding 1): See [in the matplotlib FAQ](http://matplotlib.org/faq/usage_faq.html#matplotlib-pyplot-and-pylab-how-are-they-related). pylab is pyplot directly imported into the namespace, hence they are the same in that respect. If you desperately have to stick to pylab, you could try app.Destroy() (no warranties included). 2) Show an example for wx.ID_EXIT which works in a wx.MenuItem automagically (always willing to learn). – nepix32 Jan 24 '15 at 21:22
0

I agree with the answer from @nepix32 on not using pylab but use the OOP approach of Matplotlib. However, in my case, I still need the matplotlib.use('WX') for the application to show.

I have the same problem of wxpython GUI application not terminated properly if a Matplotlib figure is displayed even if I'm not using pylab or pyplot.

My workaround is to pass the app object to the wx.Frame class and call app.Exit at EVT_CLOSE.

class my_frame(wx.Frame):
    def __init__(self, app=None):
        ...
        self.InitUI()
        self.Bind(wx.EVT_CLOSE, lambda evt: app.Exit())
    ...

if __name__ == '__main__':
    ...
    app.frame = my_frame(app)
    ...

I am up for anyone who can suggest cleaner solution. To be honest, I'm still new to wxpython.

Richard Wong
  • 3,498
  • 4
  • 19
  • 19