wxPython: Ensuring Only One Instance Per Frame

The other day, I came across an interesting StackOverflow question where the fellow was trying to figure out how to open a sub-frame only once. Basically he wanted a single instance of the sub-frame (and other sub-frames). After digging around a bit on Google, I found an old thread from the wxPython Google Group that had an interesting approach to doing what was needed.

Basically it required a bit of meta-programming, but it was a fun little exercise that I thought my readers would find interesting. Here’s the code:

import wx

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)


########################################################################
class SingleInstanceFrame(wx.Frame):
    """"""

    instance = None
    init = 0

    #----------------------------------------------------------------------
    def __new__(self, *args, **kwargs):
        """"""
        if self.instance is None:
            self.instance = wx.Frame.__new__(self)
        elif isinstance(self.instance, wx._core._wxPyDeadObject):
            self.instance = wx.Frame.__new__(self)
        return self.instance

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        print id(self)
        if self.init:
            return
        self.init = 1

        wx.Frame.__init__(self, None, title="Single Instance Frame")
        panel = MyPanel(self)
        self.Show()


########################################################################
class MainFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Main Frame")
        panel = MyPanel(self)
        btn = wx.Button(panel, label="Open Frame")
        btn.Bind(wx.EVT_BUTTON, self.open_frame)
        self.Show()

    #----------------------------------------------------------------------
    def open_frame(self, event):
        frame = SingleInstanceFrame()

if __name__ == '__main__':
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()

The meat of this code is in the SingleInstanceFrame class, specifically in the __new__ method. Here we check to see if the variable self.instance is set to None. If so, we create a new instance. We will also create a new instance if the user closes the frame, which will cause it to become a wxPyDeadObject. This is what the second part of the if statement is for. It checks to see if the instance has been deleted and if it has, it creates a new instance.

You will also notice that we have a variable called self.init. This is used to check if the instance has already been initialized. If so, __init__ will just return instead of re-instantiating everything.


wxPython 4 / Phoenix

In wxPython 4 / Phoenix, there is no wx._core._wxPyDeadObject, so we have to modify our code a bit to make it work in the newer versions of wxPython. Here’s how:

import wx


class MyPanel(wx.Panel):
    """"""

    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)


class SingleInstanceFrame(wx.Frame):
    """"""

    instance = None
    init = 0

    def __new__(self, *args, **kwargs):
        """"""
        if self.instance is None:
            self.instance = wx.Frame.__new__(self)
        elif not self.instance:
            self.instance = wx.Frame.__new__(self)

        return self.instance

    def __init__(self):
        """Constructor"""
        print(id(self))
        if self.init:
            return
        self.init = 1

        wx.Frame.__init__(self, None, title="Single Instance Frame")
        panel = MyPanel(self)
        self.Show()


class MainFrame(wx.Frame):
    """"""

    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Main Frame")
        panel = MyPanel(self)
        btn = wx.Button(panel, label="Open Frame")
        btn.Bind(wx.EVT_BUTTON, self.open_frame)
        self.Show()

    def open_frame(self, event):
        frame = SingleInstanceFrame()


if __name__ == '__main__':
    print wx.version()
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()

You will note that the only difference is found in the __new__ method where we changed the conditional statements slightly.

I hope you’ve found this tutorial useful. Have fun and happy coding!

Related Reading

7 thoughts on “wxPython: Ensuring Only One Instance Per Frame”

  1. Nice example of the singleton pattern.
    To be usable as a base class __init__ should implement a protocol that calls a method like OnInit() only when the object is really created. By overriding that method subclasses can define how initialization should be done.

    Two questions:
    1 – why not use super().() instead of wx.Frame.() ?

    2 – why not use booleans (False, True) instead of ints (0, 1)?

  2. I haven’t seen a compelling reason to switch to using super() with wxPython, so I haven’t been using it there. I don’t see how it would benefit the example.

    As for the booleans, I was following another example and didn’t think to swap it out. Good idea though!

  3. Compelling reason? None.
    Just being more pythonic and not to reference external entities where you don’t have to.

  4. Is there an update to this for wxpython 4.0? the wx._core._wxPyDeadObject bit doenst seem to work anymore. I’ve also tried the SingleInstanceChecker code, this works but not how I need. I need to be able to reload a child frame after its closed…. but only have 1 ‘alive’ at a time.

  5. Oh NICE!!!! thank you, I’ve been searching high and low for a solution. I’ll check it out.

Comments are closed.