Question

I have a wizard created using PyWizardPage. It starts with a main page, and then the user is brought through two more pages asking him for input information. One one of these pages, I've created two Browse buttons which ask the user to select two files. Now, on this page, I want the Next button to be disabled until the user selects the files.

I have scoured this site and Google for answers and have read things like this and this but can't seem to get my setup to work.

How do I disable just one button until the two Browse buttons are filled in?

As for my setup, I have a WizardPage class that I call to create each page in my wizard.

mywizard.py

import wx
import wx.wizard

class WizardPage(wx.wizard.PyWizardPage):
    def __init__(self, parent, title):
        wx.wizard.PyWizardPage.__init__(self, parent)
        self.next = None
        self.prev = None
        self.initializeUI(title)

    def initializeUI(self, title):      
        # create grid layout manager    
        self.sizer = wx.GridBagSizer()
        self.SetSizerAndFit(self.sizer)

    def addWidget(self, widget, pos, span): 
        self.sizer.Add(widget, pos, span, wx.EXPAND)

    # getters and setters 
    def SetPrev(self, prev):
        self.prev = prev

    def SetNext(self, next):
        self.next = next

    def GetPrev(self):
        return self.prev

    def GetNext(self):
        return self.next

Then, in a separate file, I have a function to create each page. The function for the first page calls the functions to create the other pages. I also use helper functions called "browse1" and "browse2" to open a wx.FileDialog and such.

pages.py

import wx
from mywizard import * 

def create_page1(wizard):
    page1 = WizardPage(wizard, "Page 1")
    d = wx.StaticText(...)
    page1.addWidget(d, (2, 1), (1,5))

    page2 = create_update_certs_page2(wizard)
    page3 = create_update_certs_page3(wizard)

    # Set links
    page2.SetPrev(page1)
    page1.SetNext(page2)
    page3.SetPrev(page2)
    page2.SetNext(page3)

    return page1

def create_page2(wizard):
    page2 = WizardPage(wizard, "Page 2")

    b1 = wx.Button(page2, -1, "Browse...", wx.DefaultPosition, (85, 23))
    b1.Bind(wx.EVT_BUTTON, lambda evt: browse1(evt, page2)) 
    x1 = wx.TextCtrl(...) # the text area where the Browse path is populated

    b2 = wx.Button(page2, -1, "Browse...", wx.DefaultPosition, (85, 23))
    b2.Bind(wx.EVT_BUTTON, lambda evt: browse2(evt, page2)) 
    x2 = wx.TextCtrl(...) # the text area where the Browse path is populated


    page2.addWidget(b1, ..., ...)
    page2.addWidget(b2, ..., ...)

    return page2

And the main code in pages.py is:

app = wx.App(redirect = False)
wizard = wx.wizard.Wizard(None, -1, "Some Title")
wizard.SetPageSize((500, 350))

mypage1 = create_page1(wizard)

# Let's go!
wizard.RunWizard(mypage1)

Now, it is under the create_page2 function of pages.py that I want to put the code to disable the Next button until the two Browse buttons are filled in.

Doing this disables all Next buttons in the wizard:

wizard.FindWizardById(wx.ID_FORWARD).Disable()

Doing this also disables all Next buttons in the wizard:

for o in wizard.GetChildren():
    if 'Button' in str(type(o)) and o.GetLabel() == "&Next >" and x1.GetValue() == "" and x2.GetValue() == "":
    o.Disable()

Any help would be greatly appreciated.

EDIT:

Okay, so I tinkered with this a bit more and my code looks similar to Mike Driscoll's answer below. Here is my setup. I have three classes: WizardPage which creates a regular page, MyWizard which creates the first page and calls helper functions to create and link all the subsequent pages, and WizardPageDisabled which is the same as WizardPage but has the timer that you introduced with the ability to disable the Next button. I added this last class because this functionality may be needed for pages that's not just the first page of the wizard.

Now, I've come to a part when I see a problem. WizardPage uses the PyWizardPage constructor and MyWizard uses the wx.Wizard constructor. If I create WizardPageDisabled using the PyWizardPage constructor, the line forwardBtn.Disable() returns the NoneType has no attribute Disable error. If I use the wx.Wizard constructor, the page doesn't show up at all.

So in addition to my issue above, I guess another clarification question is: what is the difference between PyWizardPage and wx.Wizard?

Was it helpful?

Solution

This is a sneaky one. I'm sure there's more than one way to do this, but the way I came up with uses a wx.Timer to check and see if the text controls have values. If they do, then then re-enable the button. By the way, the answer I gave (which you linked) for disabling the button some time ago does work. Joran was correct...

Anyway, here is my implementation:

import wx
import wx.wizard

class WizardPage(wx.wizard.PyWizardPage):
    def __init__(self, parent, title):
        wx.wizard.PyWizardPage.__init__(self, parent)
        self.next = None
        self.prev = None
        self.initializeUI(title)

    def initializeUI(self, title):      
        # create grid layout manager    
        self.sizer = wx.GridBagSizer()
        self.SetSizerAndFit(self.sizer)

    def addWidget(self, widget, pos, span): 
        self.sizer.Add(widget, pos, span, wx.EXPAND)

    # getters and setters 
    def SetPrev(self, prev):
        self.prev = prev

    def SetNext(self, next):
        self.next = next

    def GetPrev(self):
        return self.prev

    def GetNext(self):
        return self.next

########################################################################
class MyWizard(wx.wizard.Wizard):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.wizard.Wizard.__init__(self, None, -1, "Some Title")
        self.SetPageSize((500, 350))

        mypage1 = self.create_page1()

        forward_btn = self.FindWindowById(wx.ID_FORWARD) 
        forward_btn.Disable()

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onUpdate, self.timer)
        self.timer.Start(1)

        self.RunWizard(mypage1)

    #----------------------------------------------------------------------
    def create_page1(self):
        page1 = WizardPage(self, "Page 1")
        d = wx.StaticText(page1, label="test")
        page1.addWidget(d, (2, 1), (1,5))

        self.text1 = wx.TextCtrl(page1)
        page1.addWidget(self.text1, (3,1), (1,5))

        self.text2 = wx.TextCtrl(page1)
        page1.addWidget(self.text2, (4,1), (1,5))

        page2 = WizardPage(self, "Page 2")
        page3 = WizardPage(self, "Page 3")

        # Set links
        page2.SetPrev(page1)
        page1.SetNext(page2)
        page3.SetPrev(page2)
        page2.SetNext(page3)

        return page1

    #----------------------------------------------------------------------
    def onUpdate(self, event):
        """
        Enables the Next button if both text controls have values
        """
        value_one = self.text1.GetValue()
        value_two = self.text2.GetValue()
        if value_one and value_two:
            forward_btn = self.FindWindowById(wx.ID_FORWARD) 
            forward_btn.Enable()
            self.timer.Stop()

#----------------------------------------------------------------------
def main():
    """"""
    wizard = MyWizard()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    main()
    app.MainLoop()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top