I'm currently making a Python application with wxWigets that has two windows. The first one is the main "controller" window, and the second one is intended to be a data display window.
I want to have some mechanism in place for the first window to know wherever the second window was already spawned, and if yes, if it was closed by the user. I though about using Python's weakref.proxy(), since based on my little understanding of the language, it seemed that if an object is closed/destroyed/deallocated/GC'ed, any attempts to call my proxy would return a None
value, which could be conveniently checked with Python's is None
/ is not None
operators.
As long as the window is spawned once, the proxy works as intended, and returns None
if the window is not yet spawned, or a reference to the object otherwise. But as soon as I close the secondary window, the proxy object won't revert to None
as expected, and my application will crash with a ReferenceError: weakly-referenced object no longer exists
.
I remember trying to solve this previously and the most functioning solution I found was checking the object's class name against an internal wx class, like:
if windowObject.__class__.__name__ is not "_wxPyDeadObject": #do stuff
This, however, seems like a very hackish solution to me, and I'd like to know if there's any better way out other than the above. Below is some basic code which reproduces this issue of mine.
import wx
import weakref
class SillyWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title="Spawned Window")
self.Show()
class ExWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title="Main Window")
self.panel = wx.Panel(self)
self.button = wx.Button(self.panel, label="Spawn window!")
self.Bind(wx.EVT_BUTTON, self.spawn, self.button)
self.txt = wx.TextCtrl(self.panel, pos=(0,100))
self.wind = None
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.timer.Start(50)
self.Show()
def spawn(self,event):
if self.wind is None: # Preventing multiple spawning windows
self.wind = weakref.proxy(SillyWindow())
def update(self,event):
if self.wind is not None:
self.txt.SetValue(str(self.wind))
else:
self.txt.SetValue("None")
app = wx.App(False)
frame = ExWindow()
app.MainLoop()
As you've seen, when a wx widget object has been destroyed the Python proxy object's class is swapped with one that changes it to raise an exception when you try to use it. It also has a __nonzero__
method so you can do things like this instead of digging into the guts of the object to find it's class name:
if not windowObject:
# it has been destroyed already
return
Another thing to keep in mind is that top-level windows are not destroyed at the time they are closed or their Destroy
method has been called. Instead they are added to a pending delete list which is processed as soon as there are no more pending events. You can test for that case (closed but not yet destroyed) by calling the frame's IsBeingDeleted
method. Also, the C++ parts of the UI objects hold their own reference to the Python object too, although that will be decref'd when the C++ object is destroyed. So some or all of these things could be interfering with your weafref approach. Personally I would just use an if
statement like the above. Short. Sweet. Simple.
P.S. Some of the details I've mentioned here are specific to wxPython Classic, and are not handled the same in Phoenix. However using an if
statement like the above still works.