diff --git a/fsl/fslview/displaycontext/displaycontext.py b/fsl/fslview/displaycontext/displaycontext.py
index 8ab23f27edef7f881a7eca9d1957f33669bfe4df..71c45aae78c2d4957d8ccfb7f7613b53f1bad468 100644
--- a/fsl/fslview/displaycontext/displaycontext.py
+++ b/fsl/fslview/displaycontext/displaycontext.py
@@ -97,6 +97,8 @@ class DisplayContext(props.SyncableHasProperties):
         
     def destroy(self):
 
+        self.__overlayList.removeListener('overlays', self.__name)
+
         for overlay, display in self.__displays.items():
             display.destroy()
 
diff --git a/fsl/fslview/frame.py b/fsl/fslview/frame.py
index 4bd7990b2c6229e230a8a003bd062c1f6bbc7788..440624556dd927160fbc6a93b3c5f03fbe6a7b60 100644
--- a/fsl/fslview/frame.py
+++ b/fsl/fslview/frame.py
@@ -202,7 +202,8 @@ class FSLViewFrame(wx.Frame):
             menuBar.Remove(menuIdx)
 
         # Calling fslpanel.FSLViewPanel.destroy()
-        # - I think that the AUINotebook does the
+        # and DisplayContext.destroy() - the
+        # AUINotebook should do the
         # wx.Window.Destroy side of things ...
         panel.destroy()
         dctx .destroy()
diff --git a/fsl/fslview/gl/lightboxcanvas.py b/fsl/fslview/gl/lightboxcanvas.py
index e3b08e174b65aa429f2f9d84151e4717de380a4f..219058c1c89c573dc5b26a8ceeb1d03af5994b75 100644
--- a/fsl/fslview/gl/lightboxcanvas.py
+++ b/fsl/fslview/gl/lightboxcanvas.py
@@ -212,6 +212,22 @@ class LightBoxCanvas(slicecanvas.SliceCanvas):
                          '{}_zPosChanged'.format(self.name),
                          self._zPosChanged)
 
+    def destroy(self):
+
+        self.removeListener('pos', '{}_zPosChanged'.format(self.name))
+        self.removeListener('sliceSpacing',                self.name)
+        self.removeListener('ncols',          self.name)
+        self.removeListener('nrows',          self.name)
+        self.removeListener('zrange',         self.name)
+        self.removeListener('showGridLines',  self.name)
+        self.removeListener('highlightSlice', self.name)
+        self.removeListener('topRow',         self.name)
+        
+        if self._offscreenRenderTexture is not None:
+            self._offscreenRenderTexture.destroy()
+        
+        slicecanvas.SliceCanvas.destroy(self)
+
         
     def _topRowChanged(self, *a):
         """Called when the :attr:`topRow` property changes.  Adjusts display
diff --git a/fsl/fslview/gl/slicecanvas.py b/fsl/fslview/gl/slicecanvas.py
index e50a14797d295371e8af521feb319b0f0344745d..6da93047997727b833b6344d19c007afa9977d4b 100644
--- a/fsl/fslview/gl/slicecanvas.py
+++ b/fsl/fslview/gl/slicecanvas.py
@@ -308,6 +308,47 @@ class SliceCanvas(props.HasProperties):
                                      self._overlayBoundsChanged)
 
 
+    def destroy(self):
+        """This method must be called when this ``SliceCanvas`` is no longer
+        being used.
+
+        It removes listeners from all :class:`.OverlayList`,
+        :class:`.DisplayContext`, and :class:`.Display` instances, and
+        destroys OpenGL representations of all overlays.
+        """
+        self.removeListener('zax',           self.name)
+        self.removeListener('pos',           self.name)
+        self.removeListener('displayBounds', self.name)
+        self.removeListener('showCursor',    self.name)
+        self.removeListener('invertX',       self.name)
+        self.removeListener('invertY',       self.name)
+        self.removeListener('zoom',          self.name)
+        self.removeListener('renderMode',    self.name)
+
+        self.overlayList.removeListener('overlays',     self.name)
+        self.displayCtx .removeListener('bounds',       self.name)
+        self.displayCtx .removeListener('overlayOrder', self.name)
+
+        for overlay in self.overlayList:
+            disp  = self.displayCtx.getDisplay(overlay)
+            globj = self._glObjects[overlay]
+
+            disp.removeListener('overlayType',  self.name)
+            disp.removeListener('enabled',      self.name)
+            disp.unbindProps(   'softwareMode', self)
+
+            globj.destroy()
+
+            rt, rtName = self._prerenderTextures.get(overlay, (None, None))
+            ot         = self._offscreenTextures.get(overlay, None)
+
+            if rt is not None: glresources.delete(rtName)
+            if ot is not None: ot         .destroy()
+
+        self.overlayList = None
+        self.displayCxt  = None
+            
+
     def _initGL(self):
         """Call the _overlayListChanged method - it will generate
         any necessary GL data for each of the overlays
@@ -516,7 +557,7 @@ class SliceCanvas(props.HasProperties):
         self._refresh()
 
 
-    def __genGLObject(self, overlay, display, updateRenderTextures=True):
+    def __genGLObject(self, overlay, updateRenderTextures=True):
         """Creates a :class:`.GLObject` instance for the given ``overlay``,
         destroying any existing instance.
 
@@ -525,6 +566,8 @@ class SliceCanvas(props.HasProperties):
         render texture associated with the overlay is destroyed.
         """
 
+        display = self.displayCtx.getDisplay(overlay)
+
         # Tell the previous GLObject (if
         # any) to clean up after itself
         globj = self._glObjects.pop(overlay, None)
@@ -559,6 +602,19 @@ class SliceCanvas(props.HasProperties):
 
         self._glObjects[overlay] = globj
 
+        if not display.isBound('softwareMode', self):
+            display.bindProps('softwareMode', self)
+
+        display.addListener('overlayType',
+                            self.name,
+                            self.__overlayTypeChanged,
+                            overwrite=True)
+
+        display.addListener('enabled',
+                            self.name,
+                            self._refresh,
+                            overwrite=True)
+
         return globj
  
             
@@ -590,18 +646,7 @@ class SliceCanvas(props.HasProperties):
             if overlay in self._glObjects:
                 continue
 
-            display = self.displayCtx.getDisplay(overlay)
-
-            self.__genGLObject(overlay, display, updateRenderTextures=False)
-
-            # Bind Display.softwareMode to SliceCanvas.softwareMode
-            display.bindProps('softwareMode', self)
-
-            display.addListener('overlayType',
-                                self.name,
-                                self.__overlayTypeChanged)
-            
-            display.addListener('enabled', self.name, self._refresh)
+            self.__genGLObject(overlay, updateRenderTextures=False)
 
         self._updateRenderTextures()
         self._resolutionLimitChange()
@@ -919,7 +964,7 @@ class SliceCanvas(props.HasProperties):
                 continue
             
             if globj is None:
-                globj = self.__genGLObject(overlay, display)
+                globj = self.__genGLObject(overlay)
 
             # On-screen rendering - the globject is
             # rendered directly to the screen canvas
diff --git a/fsl/fslview/gl/wxgllightboxcanvas.py b/fsl/fslview/gl/wxgllightboxcanvas.py
index 60a9074c2fbaf08a51093a3bbcfa49a5fc6ca86d..75d1fe1589dbfce1e76f4ba5ce423e0fe40a0696 100644
--- a/fsl/fslview/gl/wxgllightboxcanvas.py
+++ b/fsl/fslview/gl/wxgllightboxcanvas.py
@@ -8,17 +8,12 @@
 :class:`.LightBoxCanvas`, and a :class:`wx.glcanvas.GLCanvas`.
 """
 
-import logging
 
 import wx
 import wx.glcanvas              as wxgl
 
 import lightboxcanvas           as lightboxcanvas
 import fsl.fslview.gl           as fslgl
-import fsl.fslview.gl.resources as glresources
-
-
-log = logging.getLogger(__name__)
 
 
 class WXGLLightBoxCanvas(lightboxcanvas.LightBoxCanvas,
@@ -41,58 +36,6 @@ class WXGLLightBoxCanvas(lightboxcanvas.LightBoxCanvas,
                                                displayCtx,
                                                zax)
         fslgl.WXGLCanvasTarget       .__init__(self)
-        
-        # the overlay list is probably going to outlive
-        # this SliceCanvas object, so we do the right
-        # thing and remove our listeners when we die
-        def onDestroy(ev):
-            ev.Skip()
-
-            if ev.GetEventObject() is not self:
-                return
-
-            self.removeListener('zax',            self.name)
-            self.removeListener('pos',            self.name)
-            self.removeListener('pos',
-                                '{}_zPosChanged'.format(self.name))
-            self.removeListener('displayBounds',  self.name)
-            self.removeListener('showCursor',     self.name)
-            self.removeListener('invertX',        self.name)
-            self.removeListener('invertY',        self.name)
-            self.removeListener('zoom',           self.name)
-            self.removeListener('sliceSpacing',   self.name)
-            self.removeListener('ncols',          self.name)
-            self.removeListener('nrows',          self.name)
-            self.removeListener('zrange',         self.name)
-            self.removeListener('showGridLines',  self.name)
-            self.removeListener('highlightSlice', self.name)
-            self.removeListener('topRow',         self.name)
-            self.removeListener('renderMode',     self.name)
-
-            self.overlayList.removeListener('overlays',     self.name)
-            self.displayCtx .removeListener('bounds',       self.name)
-            self.displayCtx .removeListener('overlayOrder', self.name)
-            
-            for overlay in self.overlayList:
-                
-                disp  = self.displayCtx.getDisplay(overlay)
-                globj = self._glObjects[overlay]
-
-                disp.removeListener('overlayType',   self.name)
-                disp.removeListener('enabled',       self.name)
-                disp.removeListener('softwareMode',  self.name)
-
-                globj.destroy()
-
-                rt, rtName = self._prerenderTextures.get(overlay, (None, None))
-
-                if rt is not None:
-                    glresources.delete(rtName)
-
-            if self._offscreenRenderTexture is not None:
-                self._offscreenRenderTexture.destroy()
-
-        self.Bind(wx.EVT_WINDOW_DESTROY, onDestroy)
 
         # When the canvas is resized, we have to update
         # the display bounds to preserve the aspect ratio
diff --git a/fsl/fslview/gl/wxglslicecanvas.py b/fsl/fslview/gl/wxglslicecanvas.py
index ad0778e06e8062ac6f1552f132e1530fcb026e5b..502679b7f21a6999e3875d391afd7efa1d9ffdda 100644
--- a/fsl/fslview/gl/wxglslicecanvas.py
+++ b/fsl/fslview/gl/wxglslicecanvas.py
@@ -13,17 +13,12 @@ data (although most of the functionality is provided by the
 :class:`.SliceCanvas` class).
 """
 
-import logging
 
 import wx
-import wx.glcanvas              as wxgl
+import wx.glcanvas    as wxgl
 
-import slicecanvas              as slicecanvas
-import fsl.fslview.gl.resources as glresources
-import fsl.fslview.gl           as fslgl
-
-
-log = logging.getLogger(__name__)
+import slicecanvas    as slicecanvas
+import fsl.fslview.gl as fslgl
 
 
 class WXGLSliceCanvas(slicecanvas.SliceCanvas,
@@ -43,46 +38,6 @@ class WXGLSliceCanvas(slicecanvas.SliceCanvas,
         wxgl.GLCanvas          .__init__(self, parent)
         slicecanvas.SliceCanvas.__init__(self, overlayList, displayCtx, zax)
         fslgl.WXGLCanvasTarget .__init__(self)
-        
-        # the image list is probably going to outlive
-        # this SliceCanvas object, so we do the right
-        # thing and remove our listeners when we die
-        def onDestroy(ev):
-            ev.Skip()
-
-            if ev.GetEventObject() is not self:
-                return
-
-            self.removeListener('zax',           self.name)
-            self.removeListener('pos',           self.name)
-            self.removeListener('displayBounds', self.name)
-            self.removeListener('showCursor',    self.name)
-            self.removeListener('invertX',       self.name)
-            self.removeListener('invertY',       self.name)
-            self.removeListener('zoom',          self.name)
-            self.removeListener('renderMode',    self.name)
-            
-            self.overlayList.removeListener('overlays',     self.name)
-            self.displayCtx .removeListener('bounds',       self.name)
-            self.displayCtx .removeListener('overlayOrder', self.name)
-            
-            for overlay in self.overlayList:
-                disp  = self.displayCtx.getDisplay(overlay)
-                globj = self._glObjects[overlay]
-                    
-                disp.removeListener('overlayType',  self.name)
-                disp.removeListener('enabled',      self.name)
-                disp.removeListener('softwareMode', self.name)
-
-                globj.destroy()
-
-                rt, rtName = self._prerenderTextures.get(overlay, (None, None))
-                ot         = self._offscreenTextures.get(overlay, None)
-
-                if rt is not None: glresources.delete(rtName)
-                if ot is not None: ot   .destroy()
-
-        self.Bind(wx.EVT_WINDOW_DESTROY, onDestroy)
 
         # When the canvas is resized, we have to update
         # the display bounds to preserve the aspect ratio
diff --git a/fsl/fslview/views/lightboxpanel.py b/fsl/fslview/views/lightboxpanel.py
index 54178ab81f6d301bfcadeafdab130517d9aa4349..616e3ee7a165dc6064082c5686202b6a2396b132 100644
--- a/fsl/fslview/views/lightboxpanel.py
+++ b/fsl/fslview/views/lightboxpanel.py
@@ -138,6 +138,8 @@ class LightBoxPanel(canvaspanel.CanvasPanel):
         self._displayCtx .removeListener('selectedOverlay', self._name)
         self._overlayList.removeListener('overlays',        self._name)
 
+        self._lbCanvas.destroy()
+
         canvaspanel.CanvasPanel.destroy(self)
 
         
diff --git a/fsl/fslview/views/orthopanel.py b/fsl/fslview/views/orthopanel.py
index d503048ac44d99c1ed5ff3274cd49b44a9816aad..567f29e840895d9dc4b9c1e95a9cc1896d470547 100644
--- a/fsl/fslview/views/orthopanel.py
+++ b/fsl/fslview/views/orthopanel.py
@@ -184,7 +184,6 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         this OrthoPanel. So when this panel is destroyed, all those
         registered listeners are removed.
         """
-        canvaspanel.CanvasPanel.destroy(self)
 
         self._displayCtx .removeListener('location',        self._name)
         self._displayCtx .removeListener('bounds',          self._name)
@@ -192,6 +191,10 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         self._displayCtx .removeListener('overlayOrder',    self._name)
         self._overlayList.removeListener('overlays',        self._name)
 
+        self._xcanvas.destroy()
+        self._ycanvas.destroy()
+        self._zcanvas.destroy()
+
         # The _overlayListChanged method adds
         # listeners to individual overlays,
         # so we have to remove them too
@@ -199,6 +202,8 @@ class OrthoPanel(canvaspanel.CanvasPanel):
             opts = self._displayCtx.getOpts(ovl)
             opts.removeGlobalListener(self._name)
 
+        canvaspanel.CanvasPanel.destroy(self)
+
             
     def __onZoom(self, *a):
         """Called when the :attr:`.SceneOpts.zoom` property changes.