Why does this script crash Python on OS X?

The script below crashes Python on OS X (EPD 7.0-2 & 7.1.2) when the
ComboBox is used. Similar code would also crash python on Windows as
well, but somehow in the process of reducing this from a ~10,000
program to a ~100 line example, the Windows problem went away.

The code reconfigures the contents of a ScrolledWindow. Since on OS X,
the ScrolledWindow will have scrollbars as children, a panel is placed
inside the ScrolledWindow and I use various methods to clear that out,
so that I can place new stuff inside (see comments), but nothing I I
have tried works properly. Is there some extra step that is needed to
safely destroy widgets?

Brian

···

#================================================================
import wx

def OnSizeType(event):
    w = event.GetEventObject()
    topframe =
w.GetParent().GetParent().GetParent().GetParent().GetParent()
    topframe.Mode = w.GetValue()
    updateDataPanel(topframe)

def updateDataPanel(topframe):
    def TopSizer(topframe):
        topSizer = wx.BoxSizer(wx.HORIZONTAL)
        sizeType = wx.ComboBox(topframe.dataPanel,wx.ID_ANY,
                               value=topframe.Mode,
                               choices=['isotropic','ellipsoidal'],
                               style=wx.CB_READONLY|wx.CB_DROPDOWN)
        sizeType.Bind(wx.EVT_COMBOBOX, OnSizeType)
        topSizer.Add(sizeType)
        return topSizer
    def IsoSizer(topframe):
        isoSizer = wx.BoxSizer(wx.HORIZONTAL)
        sizeVal = wx.TextCtrl(topframe.dataPanel,wx.ID_ANY,
            '1.1',style=wx.TE_PROCESS_ENTER)
        isoSizer.Add(sizeVal,0,wx.ALIGN_CENTER_VERTICAL)
        return isoSizer
    def EllSizeDataSizer(topframe):
        dataSizer = wx.FlexGridSizer(1,6,5,5)
        for Pa,val,ref in zip(['S11','S22','S33','S12','S13','S23'],
                              [5.0,5.1,5.2,5.3,5.4,5.5],
                              [True,] + 5*[False,]):
            sizeVal = wx.TextCtrl(topframe.dataPanel,wx.ID_ANY,'%.3f'%
(val),
                                  style=wx.TE_PROCESS_ENTER)
            dataSizer.Add(sizeVal,0,wx.ALIGN_CENTER_VERTICAL)
        return dataSizer

    if topframe.dataPanel is None:
        topframe.dataPanel = wx.Panel(topframe.dataScroll)
    else:
        # cleanup attempt #1 -- delete sizer contents. Crashes python
on OS X
        topframe.dataPanel.GetSizer().Clear(True)

        # cleanup attempt #2 -- delete panel's children. Crashes
python on OS X
        #topframe.dataPanel.DestroyChildren()

        # cleanup attempt #3 -- delete panel. Crashes python on OS X
        #topframe.dataPanel.Destroy()
        #topframe.dataPanel = wx.Panel(topframe.dataScroll)

        # cleanup attempt #4 -- remove sizer contents. Windows remain
        #topframe.dataPanel.GetSizer().Clear()

        # cleanup attempt #5 -- do nothing here but use deleteOld
option on SetSizer
        # children are not shown, but keep piling up
        #print 'Panel child
count:',len(topframe.dataPanel.GetChildren())

    mainSizer = wx.BoxSizer(wx.VERTICAL)
    if topframe.Mode == 'isotropic':
        isoSizer = wx.BoxSizer(wx.HORIZONTAL)
        isoSizer.Add(TopSizer(topframe),0,wx.ALIGN_CENTER_VERTICAL)
        isoSizer.Add(IsoSizer(topframe),0,wx.ALIGN_CENTER_VERTICAL)
        mainSizer.Add(isoSizer)
    elif topframe.Mode == 'ellipsoidal':
        ellSizer = wx.BoxSizer(wx.HORIZONTAL)
        ellSizer.Add(TopSizer(topframe),0,wx.ALIGN_CENTER_VERTICAL)
        mainSizer.Add(ellSizer)
        mainSizer.Add(EllSizeDataSizer(topframe))

    #topframe.dataPanel.SetSizer(mainSizer,deleteOld=True)
    topframe.dataPanel.SetSizer(mainSizer)
    mainSizer.Fit(topframe.childFrame)
    Size = mainSizer.GetMinSize()
    Size[0] += 40
    Size[1] = max(Size[1],250) + 20
    topframe.dataPanel.SetSize(Size)
    topframe.dataScroll.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
    Size[1] = min(Size[1],450)
    topframe.childFrame.setSizePosLeft(Size)

def UpdatePhaseData(topframe):
    topframe.dataScroll = wx.ScrolledWindow(topframe.childNB)
    topframe.childNB.AddPage(topframe.dataScroll,'Data')
    Texture = wx.ScrolledWindow(topframe.childNB)
    topframe.childNB.AddPage(Texture,'Texture')

    updateDataPanel(topframe)

class ChildFrame(wx.Frame):
    def __init__(self,parent):
        wx.Frame.__init__(self,parent=parent,
                          style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
        self.Show()

    def setSizePosLeft(self,Width):
        clientSize = wx.ClientDisplayRect()
        Width[1] = min(Width[1],clientSize[2]-300)
        Width[0] = max(Width[0],300)
        self.SetSize(Width)
        self.SetPosition(wx.Point(clientSize[2]-
Width[0],clientSize[1]+250))

class GSNoteBook(wx.Notebook):
    def __init__(self, parent):
        wx.Notebook.__init__(self, parent, -1, name='', style=
wx.BK_TOP)
        self.SetSize(parent.GetClientSize())

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, parent=None, title = u'Main window')
        self.childFrame = ChildFrame(parent=self)
        self.childFrame.SetMenuBar(wx.MenuBar())
        self.childFrame.SetLabel('Child Frame')
        self.childFrame.CreateStatusBar()
        self.childNB = GSNoteBook(parent=self.childFrame)
        self.dataScroll = None
        self.dataPanel = None
        self.Mode = 'isotropic'

        UpdatePhaseData(self)

if __name__ == '__main__':
    import sys
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show(True)
    app.MainLoop()

<snip>

#================================================================
import wx

def OnSizeType(event):
w = event.GetEventObject()
topframe =
w.GetParent().GetParent().GetParent().GetParent().GetParent()

^^^^^^

You have got to be kidding... 5 GetParent() in a row is the absolute
record up to now. I would suggest you to use wx.GetTopLevelParent(w)
if "topframe" is an instance of wx.Frame.

Other than that, I am sorry I can't offer a solution as I am not a Mac
person. However, as a recommendation, I would suggest you to post a
small, runnable sample so we may actually be able to run your code and
check for problems.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

···

On 8 December 2011 19:11, bht wrote:

Hi,

The script below crashes Python on OS X (EPD 7.0-2 & 7.1.2) when the
ComboBox is used. Similar code would also crash python on Windows as
well, but somehow in the process of reducing this from a ~10,000
program to a ~100 line example, the Windows problem went away.

The code reconfigures the contents of a ScrolledWindow. Since on OS X,
the ScrolledWindow will have scrollbars as children, a panel is placed
inside the ScrolledWindow and I use various methods to clear that out,
so that I can place new stuff inside (see comments), but nothing I I
have tried works properly. Is there some extra step that is needed to
safely destroy widgets?

Brian

#================================================================
import wx

def OnSizeType(event):
w = event.GetEventObject()
topframe =
w.GetParent().GetParent().GetParent().GetParent().GetParent()

If your looking for the toplevel window (i.e the parent Frame) This
can be simplifed to this

topframe = w.TopLevelParent

The crash is probably because you are creating most of the widgets as
children of the Frame then adding them to a sizer that belongs to
panel that is a child (or grandchild, ...) of the Frame.

The hierarchy is all out of line, windows should be children of of the
window they will be displayed in/on, and added to the sizers of their
direct parent.

i.e) Pseudo-code

MyFrame
    - MyPanel (child of MyFrame)
      - ComboBox (child of MyPanel)

class MyFrame(wx.Frame):
     def __init__(...):
          super()
          panel = MyPanel(self)
          sizer = wx.BoxSizer()
          sizer.Add(panel)
          self.SetSizer(sizer)

class MyPanel(wx.Panel):
      def __init__(...):
           super()
           combo = wx.ComboBox(self)
           sizer = wx.BoxSizer()
           sizer.Add(combo)
           self.SetSizer(sizer)

Cody

···

On Thu, Dec 8, 2011 at 12:11 PM, bht <brian.toby@anl.gov> wrote:

<snip>

#================================================================
import wx

def OnSizeType(event):
    w = event.GetEventObject()
    topframe =
w.GetParent().GetParent().GetParent().GetParent().GetParent()

^^^^^^

You have got to be kidding... 5 GetParent() in a row is the absolute
record up to now.

Indeed! this is very fragile coding structure. See the "Law Of Demeter":

Your code will be very hard to re-use or re-factor -- a given Window should not need to know very much at all about where it lives in the hierarchy of windows.

I would suggest you to use wx.GetTopLevelParent(w)
if "topframe" is an instance of wx.Frame.

Even better, have some system for keeping track of application-level information that your Window can use -- maybe what you need isn't the in the Top-level frame (or won't be later, when you re-factor)

-Chris

···

On 12/8/11 11:11 AM, Andrea Gavana wrote:

On 8 December 2011 19:11, bht wrote:

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Hi,

The script below crashes Python on OS X (EPD 7.0-2& 7.1.2) when the
ComboBox is used. Similar code would also crash python on Windows as
well, but somehow in the process of reducing this from a ~10,000
program to a ~100 line example, the Windows problem went away.

The code reconfigures the contents of a ScrolledWindow. Since on OS X,
the ScrolledWindow will have scrollbars as children, a panel is placed
inside the ScrolledWindow and I use various methods to clear that out,
so that I can place new stuff inside (see comments), but nothing I I
have tried works properly. Is there some extra step that is needed to
safely destroy widgets?

Brian

#================================================================
import wx

def OnSizeType(event):
    w = event.GetEventObject()
    topframe =
w.GetParent().GetParent().GetParent().GetParent().GetParent()

If your looking for the toplevel window (i.e the parent Frame) This
can be simplifed to this

topframe = w.TopLevelParent

The crash is probably because you are creating most of the widgets as
children of the Frame then adding them to a sizer that belongs to
panel that is a child (or grandchild, ...) of the Frame.

Based on the OSX Crash Report I would guess that a ComboBox is being destroyed while there are still events for it in the event queue. When it tries to deliver the events then it accesses a pointer that is no longer valid. If you want to ensure that there are no pending events then you can do the destroy and recreation of widgets in an idle handler, or via a function called with wx.CallAfter.

The hierarchy is all out of line, windows should be children of of the
window they will be displayed in/on, and added to the sizers of their
direct parent.

But mis-parenting of widgets is also a common newbie mistake that can cause problems like this, so check into that too. Using the WIT is helpful for this: http://wiki.wxpython.org/Widget_Inspection_Tool

···

On 12/8/11 11:21 AM, Cody wrote:

On Thu, Dec 8, 2011 at 12:11 PM, bht<brian.toby@anl.gov> wrote:

--
Robin Dunn
Software Craftsman

Thanks for all the comments. For the benefit of anyone stumbling on
this thread with a similar problem, let me note that Robin was right
on the money. Postponing window destruction until there are no pending
events, through use of wx.CallAfter prevents my crash. The importance
of waiting for the idle loop before destroying windows was completely
missed by this newbie. The working version of the script is below.

The value of the WIT (http://wiki.wxpython.org/Widget_Inspection_Tool)
was also something I missed.

In my defense, the highly nested reference to topframe was a quick
kludge used to avoid what might have looked like recursion. I was
concentrating on making a small code for pasting.

Also, if anyone could point out where the child/parent or sizer use
hierarchy is out of line, I would appreciate it. I still don't see any
problems.

···

#==================================================================
import wx
import wx.lib.inspection

def updateDataPanel(top):
    topframe = top
    def OnSizeType(event):
        w = event.GetEventObject()
        topframe.Mode = w.GetValue()
        wx.CallAfter(updateDataPanel,topframe)
    def TopSizer():
        topSizer = wx.BoxSizer(wx.HORIZONTAL)
        sizeType = wx.ComboBox(topframe.dataPanel,wx.ID_ANY,
                               value=topframe.Mode,
                               choices=['isotropic','ellipsoidal'],
                               style=wx.CB_READONLY|wx.CB_DROPDOWN)
        sizeType.Bind(wx.EVT_COMBOBOX, OnSizeType)
        topSizer.Add(sizeType)
        return topSizer
    def IsoSizer():
        isoSizer = wx.BoxSizer(wx.HORIZONTAL)
        sizeVal = wx.TextCtrl(topframe.dataPanel,wx.ID_ANY,
            '1.1',style=wx.TE_PROCESS_ENTER)
        isoSizer.Add(sizeVal,0,wx.ALIGN_CENTER_VERTICAL)
        return isoSizer
    def EllSizeDataSizer():
        dataSizer = wx.FlexGridSizer(1,6,5,5)
        for Pa,val,ref in zip(['S11','S22','S33','S12','S13','S23'],
                              [5.0,5.1,5.2,5.3,5.4,5.5],
                              [True,] + 5*[False,]):
            sizeVal = wx.TextCtrl(topframe.dataPanel,wx.ID_ANY,
                                  '%.3f'%(val),
                                  style=wx.TE_PROCESS_ENTER)
            dataSizer.Add(sizeVal,0,wx.ALIGN_CENTER_VERTICAL)
        return dataSizer

    if topframe.dataPanel is None:
        topframe.dataPanel = wx.Panel(topframe.dataScroll)
    else:
        topframe.dataPanel.GetSizer().Clear(True)

    mainSizer = wx.BoxSizer(wx.VERTICAL)
    if topframe.Mode == 'isotropic':
        isoSizer = wx.BoxSizer(wx.HORIZONTAL)
        isoSizer.Add(TopSizer(),0,wx.ALIGN_CENTER_VERTICAL)
        isoSizer.Add(IsoSizer(),0,wx.ALIGN_CENTER_VERTICAL)
        mainSizer.Add(isoSizer)
    elif topframe.Mode == 'ellipsoidal':
        ellSizer = wx.BoxSizer(wx.HORIZONTAL)
        ellSizer.Add(TopSizer(),0,wx.ALIGN_CENTER_VERTICAL)
        mainSizer.Add(ellSizer)
        mainSizer.Add(EllSizeDataSizer())

    topframe.dataPanel.SetSizer(mainSizer)
    mainSizer.Fit(topframe.childFrame)
    Size = mainSizer.GetMinSize()
    Size[0] += 40
    Size[1] = max(Size[1],250) + 20
    topframe.dataPanel.SetSize(Size)
    topframe.dataScroll.SetScrollbars(
        10,10,Size[0]/10-4,Size[1]/10-1)
    Size[1] = min(Size[1],450)
    topframe.childFrame.setSizePosLeft(Size)

def UpdatePhaseData(topframe):
    topframe.dataScroll = wx.ScrolledWindow(topframe.childNB)
    topframe.childNB.AddPage(topframe.dataScroll,'Data')
    Texture = wx.ScrolledWindow(topframe.childNB)
    topframe.childNB.AddPage(Texture,'Texture')
    updateDataPanel(topframe)

class ChildFrame(wx.Frame):
    def __init__(self,parent):
        wx.Frame.__init__(
            self,parent=parent,
            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
        self.Show()

    def setSizePosLeft(self,Width):
        clientSize = wx.ClientDisplayRect()
        Width[1] = min(Width[1],clientSize[2]-300)
        Width[0] = max(Width[0],300)
        self.SetSize(Width)
        self.SetPosition(
            wx.Point(clientSize[2]-Width[0],clientSize[1]+250))

class GSNoteBook(wx.Notebook):
    def __init__(self, parent):
        wx.Notebook.__init__(self, parent, -1, name='',
                             style= wx.BK_TOP)
        self.SetSize(parent.GetClientSize())

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, parent=None, title = u'Main window')
        self.childFrame = ChildFrame(parent=self)
        self.childFrame.SetMenuBar(wx.MenuBar())
        self.childFrame.SetLabel('Child Frame')
        self.childFrame.CreateStatusBar()
        self.childNB = GSNoteBook(parent=self.childFrame)
        self.dataScroll = None
        self.dataPanel = None
        self.Mode = 'isotropic'

        UpdatePhaseData(self)

if __name__ == '__main__':
    import sys
    app = wx.PySimpleApp()
    frame = MyFrame()
    frame.Show(True)
    wx.lib.inspection.InspectionTool().Show()
    app.MainLoop()