wxPython: How to Create a Generic Wizard

The other day on StackOverflow I saw someone who was struggling with the Wizard widget from wxPython. The wizard doesn’t allow much customization when it comes to its buttons, so I decided to see how hard it would be to just write my own Wizard. This code is pretty limited, but here’s my first beta version:

import wx

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

    #----------------------------------------------------------------------
    def __init__(self, parent, title=None):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(sizer)
        
        if title:
            title = wx.StaticText(self, -1, title)
            title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
            sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
            sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
    
    
########################################################################
class WizardPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent)
        self.pages = []
        self.page_num = 0
        
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        self.panelSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
                
        # add prev/next buttons
        self.prevBtn = wx.Button(self, label="Previous")
        self.prevBtn.Bind(wx.EVT_BUTTON, self.onPrev)
        btnSizer.Add(self.prevBtn, 0, wx.ALL|wx.ALIGN_RIGHT, 5)
        
        self.nextBtn = wx.Button(self, label="Next")
        self.nextBtn.Bind(wx.EVT_BUTTON, self.onNext)
        btnSizer.Add(self.nextBtn, 0, wx.ALL|wx.ALIGN_RIGHT, 5)
        
        # finish layout
        self.mainSizer.Add(self.panelSizer, 1, wx.EXPAND)
        self.mainSizer.Add(btnSizer, 0, wx.ALIGN_RIGHT)
        self.SetSizer(self.mainSizer)
        
        
    #----------------------------------------------------------------------
    def addPage(self, title=None):
        """"""
        panel = WizardPage(self, title)
        self.panelSizer.Add(panel, 2, wx.EXPAND)
        self.pages.append(panel)
        if len(self.pages) > 1:
            # hide all panels after the first one
            panel.Hide()
            self.Layout()
            
    #----------------------------------------------------------------------
    def onNext(self, event):
        """"""
        pageCount = len(self.pages)
        if pageCount-1 != self.page_num:
            self.pages[self.page_num].Hide()
            self.page_num += 1
            self.pages[self.page_num].Show()
            self.panelSizer.Layout()
        else:
            print "End of pages!"
            
        if self.nextBtn.GetLabel() == "Finish":
            # close the app
            self.GetParent().Close()
            
        if pageCount == self.page_num+1:
            # change label
            self.nextBtn.SetLabel("Finish")
    
    #----------------------------------------------------------------------
    def onPrev(self, event):
        """"""
        pageCount = len(self.pages)
        if self.page_num-1 != -1:
            self.pages[self.page_num].Hide()
            self.page_num -= 1
            self.pages[self.page_num].Show()
            self.panelSizer.Layout()
        else:
            print "You're already on the first page!"
        
    
########################################################################
class MainFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Generic Wizard", size=(800,600))
        
        self.panel = WizardPanel(self)
        self.panel.addPage("Page 1")
        self.panel.addPage("Page 2")
        self.panel.addPage("Page 3")
        
        self.Show()
    
if __name__ == "__main__":
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()

The main frame instantiates our main panel (WizardPanel). Here is where the bulk of our code goes. It controls the paging back and forth through the Wizard pages after all. You can define the Wizard pages any way you like. In fact, what I may do in the 2nd version is make it so that I can just pass in a Panel Class of my own making since only using the Simple page I came up with is really limiting. Anyway, I add 3 pages and then I have some checks for iterating through them. I hope others find this interesting too. Have fun!

Further Reading

11 thoughts on “wxPython: How to Create a Generic Wizard”

  1. How can I customize such wizard?
    I mean, let’s suppose I want to insert a number of buttons and options (for example, take GenericMessageDialog from wxPython demo) in the second page of wizard.
    What I have to do?

  2. Subclass a panel and put whatever widgets you want to on it and then add it to the wizard.

  3. Hi Thank you for this informative guide.
    Suppose we have several WizardPages with distinct sizes, I was wondering if there is a way to make the frame resize itself to the size each WizardPage automagically?
    Thanks

  4. You might be able to use the Fit() method, but I think it would be a disservice to the user if the frame is constantly shrinking and enlarging while I’m trying to use it.

  5. Pingback: wxPython: How to Disable a Wizard’s Next Button | Hello Linux

  6. Matthew Connolly

    I have been working on a wizard using your examples as a jumping off point with a few slightly different implementations, for example I keep all the pages of my wizard in a list. I am having an incredible amount of difficulty in creating a wizard which dynamically changes size. For example, I would like to initialize the wizard to have three pages with the third page of the wizard prompting for a number, then add that number of pages to the wizard. Would anyone have any insights as to how to implement this?

  7. Wizards get complicated really quickly. If I were you, I would probably have that button open a second wizard that uses the same base class as the first. Alternatively, since you know how many pages you can potentially have, you could create the other pages and just not use them until someone presses that button. So the wizard knows that there are 6 pages, but it won’t go to the next three unless that special button is pressed. If that doesn’t help, then I would post over at the wxPython Google group with a small runnable example.

  8. Matthew Connolly

    Sorry to keep commenting on this thread, I am heavily involved in the development of a relatively simple wizard but keep running into hurdles that I simply do not have the experience to overcome. I am at the point now where I am able to call my wizard from a main frame so as to generate an object that can be used in the main frame. The issue I am having is that I am not sure how to return data from a wizard to the calling entity. I suspect that it has something to do with overloading the event “EVT_WIZARD_FINISHED” which is one of the events that the wxWizard class contains. Though I suspect that I would need to do so from the calling frame and I am, as of yet, not having a lot of luck doing so. Does anybody have any input on how exactly to return data from a wizard?

  9. I assume you are showing the wizard modally? Then you should be able to use your instance of the wizard to grab the data before you destroy it like you do with a dialog. Something like: self.my_wizard.some_widget.GetValue(). An alternative would be to use pubsub.

  10. Matthew Connolly

    I looked into this, it seems almost as if the wizard can ONLY be used modally (at least in its default state). That being said, I tried the GetValue() approach early in my endeavors to no avail. I did just try the pubsub approach and it seemed to work splendidly. I will have to read up on pubsub and get a little more savvy as to the specifics of using it properly, but this seems to be the way to go! Thank you!

  11. Pingback: wxPython: How to Disable a Wizard's Next Button - The Mouse Vs. The Python

Comments are closed.