wxPython: How to Create a Generic Wizard

Posted by Mike on July 12th, 2012 filed in Cross-Platform, Python, wxPython

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

Print Friendly

  • Easley

    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?

  • driscollis

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

  • Eltoro

    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

  • http://www.blog.pythonlibrary.org/ Mike Driscoll

    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.

  • Pingback: wxPython: How to Disable a Wizard’s Next Button | Hello Linux()