wxPython and PubSub: A Simple Tutorial

I see a number of questions on the wxPython mailing list or its IRC channel about communicating between frames and most of the time what the developer needs is the PubSub module. The Publisher / Subscriber model is a way to send messages to one or more listeners. You can read about it here. The Observer pattern is said to be based on the Publish / Subscribe pattern. In wxPython land, we have the pubsub module which can be accessed from wx.lib.pubsub. It’s actually included in wxPython, but you can also download it as a standalone module from its Source Forge. An alternative to pubsub is the PyDispatcher module.

Anyway, in this article we won’t be studying the theory behind either of these modules. Instead, we’ll use a semi-practical example in wxPython to show how to use the built-in version of pubsub to communicate between two frames. If you’re still with me at this point, then I encourage you to read on!

UPDATE: This article is for wxPython 2.8. If you happen to be using a newer version of wxPython, then you’ll want to read my newer version of this article here

How to Pass Information Between Two Frames

I’ve found that sometimes I need to open a non-modal frame to get information from a user and then pass that information back to my application’s main frame. Other times, I need to just tell one of my frames that the other one has closed. In these two cases, pubsub comes riding to the rescue. The following example will actually demonstrate a solution to both of these issues.

import wx
from wx.lib.pubsub import Publisher

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

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "Secondary Frame")
        panel = wx.Panel(self)
    
        msg = "Enter a Message to send to the main frame"
        instructions = wx.StaticText(panel, label=msg)
        self.msgTxt = wx.TextCtrl(panel, value="")
        closeBtn = wx.Button(panel, label="Send and Close")
        closeBtn.Bind(wx.EVT_BUTTON, self.onSendAndClose)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        flags = wx.ALL|wx.CENTER
        sizer.Add(instructions, 0, flags, 5)
        sizer.Add(self.msgTxt, 0, flags, 5)
        sizer.Add(closeBtn, 0, flags, 5)
        panel.SetSizer(sizer)
        
    #----------------------------------------------------------------------
    def onSendAndClose(self, event):
        """
        Send a message and close frame
        """
        msg = self.msgTxt.GetValue()
        Publisher().sendMessage(("show.mainframe"), msg)
        self.Close()
    

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

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent)
        self.frame = parent
        
        Publisher().subscribe(self.showFrame, ("show.mainframe"))
        
        self.pubsubText = wx.TextCtrl(self, value="")
        hideBtn = wx.Button(self, label="Hide")
        hideBtn.Bind(wx.EVT_BUTTON, self.hideFrame)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.pubsubText, 0, wx.ALL|wx.CENTER, 5)
        sizer.Add(hideBtn, 0, wx.ALL|wx.CENTER, 5)
        self.SetSizer(sizer)
        
    #----------------------------------------------------------------------
    def hideFrame(self, event):
        """"""
        self.frame.Hide()
        new_frame = OtherFrame()
        new_frame.Show()
        
    #----------------------------------------------------------------------
    def showFrame(self, msg):
        """
        Shows the frame and shows the message sent in the
        text control
        """
        self.pubsubText.SetValue(msg.data)
        frame = self.GetParent()
        frame.Show()
        
########################################################################
class MainFrame(wx.Frame):
 
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Pubsub Tutorial")
        panel = MainPanel(self)
        
        
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MainFrame()
    frame.Show()
    app.MainLoop()

Our first stop in the tour of this code is in the MainPanel class. Take note of the following line:

Publisher().subscribe(self.showFrame, ("show.mainframe"))

This creates a listener singleton (AKA: a receiver) that subscribes to the “show.mainframe” topic. Other parts of the program can publish to that topic and the listener will pick them up and call the “showFrame” method. To see this in action, check out the “OtherFrame” classe’s “onSendAndClose” method.

# OtherFrame Class
def onSendAndClose(self, event):
    """
    Send a message and close frame
    """
    msg = self.msgTxt.GetValue()
    Publisher().sendMessage(("show.mainframe"), msg)
    self.Close()

Here we grab the text control’s value to send back to our main frame. To send it, we call the Publisher object’s sendMessage method and pass it the topic string and the message. The message can be a list of objects or just a single object. In this case, it’s just a string. Back in the MainPanel, the showFrame method gets called. Here’s what that looks like:

# MainPanel class
def showFrame(self, msg):
    """
    Shows the frame and shows the message sent in the
    text control
    """
    self.pubsubText.SetValue(msg.data)
    frame = self.GetParent()
    frame.Show()

In this method we extract the data sent through pubsub via its data property. If we had sent multiple items using a list, we’d need to do something like msg.data[0] to get at the right item (assuming the string was in element one). The newest pubsub has a slightly different API which you can check out in its cookbook. The newest API is available as of wxPython 2.8.11.0. Note: I had some trouble creating a binary with the newest pubsub because I was using a slightly older API. See this thread for details and for some possible workarounds.

Now you know the basics of using pubsub in your project. This example shows how to communicate between two frames even when one is hidden! It also shows how to pass information from one frame to the other. Have fun!

Additional Reading