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