[wxPython] Validators in dialog boxes

Hi guys,

I'm working on a simple drawing application which is supposed to demonstrate all the main aspects of a typical wxPython app. Unfortunately, I'm stuck with on particular thing: I want to have a dialog box with a validated wxTextCtrl in it...but I've had no luck getting it to work.

It seems that wxPyValidators work fine if you don't use a wxDialog as your topmost window, and similarly wxDialogs work fine if you don't try to associate a wxPyValidator with an input field -- but when you try and mix the two you get all sorts of problems.

I've searched the mailing list archives, and found several references to people experiencing the same problem (either wxWindows crashes completely, or else you get a "Could not transfer data to window" alert box). Based on something Robin said in January of last year, I tried adding dummy TransferToWindow() and TransferFromWindow() methods to my wxPyValidator subclass, so that these methods return TRUE rather than the default value of FALSE -- but this doesn't seem to help at all. With these methods commented out or set to return FALSE, the "Could not transfer data to window" alert pops up; with them set to return TRUE, I get a hard crash in PYTHON15.DLL (I'm currently running Python 1.5.2 and wxPython 2.2.5 on a Windows ME machine -- though ideally any solution should work on all platforms and versions of Python). In desperation, I tried overriding wxWindow.TransferDataToWindow and wxWindow.TransferDataFromWindow, but it seems that these are C++ methods and can't be overridden at the Python level -- and I can't think of anything else to try.

I've copied out the relevant bits of the application and put them into a dummy standalone program, which I'll paste in below. If anyone can please explain how to get this going -- and more generally, how to implement a wxDialog subclass containing validated input fields, I'd be really grateful. The whole point of the app I'm writing is to demonstrate how to write a non-trivial wxPython application (we'll be using this program as the showcase app for the Getting Started guide), so I really need to get this working properly -- obviously, I could get around these problems by using a modal wxFrame as the topmost window rather than a wxDialog, but I want to demonstrate dialogs and validated input fields, which is why I'm persisting. Have I, perhaps, overlooked something obvious?

Any clues would be most welcome!

Thanks in advance,

  - Erik.

=========================== Python Code Follows =============================

from wxPython.wx import *

···

#----------------------------------------------------------------------------

class EditTextObjectDialog(wxDialog):
     """ Dialog box used to edit the properties of a text object.

         The user can edit the object's text, font, size, and text style.
     """

     def __init__(self, parent, title):
         """ Standard constructor.
         """
         wxDialog.__init__(self, parent, -1, title)

         self.textCtrl = wxTextCtrl(self, -1, "", style=wxTE_MULTILINE,
                                    validator=TextObjectValidator())
         extent = self.textCtrl.GetFullTextExtent("Hy")
         lineHeight = extent[1] + extent[3]
         self.textCtrl.SetSize(wxSize(-1, lineHeight * 4))

         fonts = wxFontEnumerator()
         fonts.EnumerateFacenames()
         fontList = fonts.GetFacenames()
         fontList.sort()

         self.fontCombo = wxComboBox(self, -1, "", wxDefaultPosition,
                                     wxDefaultSize, fontList,
                                     style = wxCB_READONLY)

         sizeList = ["8", "9", "10", "12", "14", "16", "18",
                     "20", "24", "32", "48", "72"]

         self.sizeCombo = wxComboBox(self, -1, "", wxDefaultPosition,
                                     wxDefaultSize, sizeList,
                                     style=wxCB_READONLY)

         comboSizer = wxBoxSizer(wxHORIZONTAL)
         comboSizer.Add(wxStaticText(self, -1, "Font:"), 0)
         comboSizer.Add(self.fontCombo)
         comboSizer.Add(5, 5)
         comboSizer.Add(wxStaticText(self, -1, "Size:"), 0)
         comboSizer.Add(self.sizeCombo)

         self.boldCheckbox = wxCheckBox(self, -1, "Bold")
         self.italicCheckbox = wxCheckBox(self, -1, "Italic")
         self.underlineCheckbox = wxCheckBox(self, -1, "Underline")

         styleSizer = wxBoxSizer(wxHORIZONTAL)
         styleSizer.Add(self.boldCheckbox, 0)
         styleSizer.Add(self.italicCheckbox, 0)
         styleSizer.Add(self.underlineCheckbox, 0)

         self.okButton = wxButton(self, wxID_OK, "OK")
         self.cancelButton = wxButton(self, wxID_CANCEL, "Cancel")

         btnSizer = wxBoxSizer(wxHORIZONTAL)
         btnSizer.Add(self.okButton)
         btnSizer.Add(self.cancelButton)

         sizer = wxBoxSizer(wxVERTICAL)
         sizer.Add(self.textCtrl, 1, wxEXPAND | wxALL, 5)
         sizer.Add(comboSizer, 0, wxALIGN_CENTRE | wxALL, 5)
         sizer.Add(styleSizer, 0, wxALIGN_CENTRE | wxALL, 5)
         sizer.Add(btnSizer, 0, wxALIGN_CENTRE | wxALL, 5)

         self.SetAutoLayout(true)
         self.SetSizer(sizer)
         sizer.Fit(self)
         sizer.SetSizeHints(self)

#----------------------------------------------------------------------------

class TextObjectValidator(wxPyValidator):
     """ This validator is used to ensure that the user has entered something
         into the text object editor dialog's text field.
     """
     def __init__(self):
         """ Standard constructor.
         """
         wxPyValidator.__init__(self)

     def Clone(self):
         """ Standard cloner.

             Note that every validator must implement the Clone() method.
         """
         return TextObjectValidator()

     def Validate(self, win):
         """ Validate the contents of the given text control.
         """
         print "Calling validate..."
         textCtrl = wxPyTypeCast(win, "wxTextCtrl")
         text = textCtrl.GetValue()

         if len(text) == 0:
             wxMessageBox("A text object must contain some text!", "Error")
             return false
         else:
             return true

     def TransferToWindow(self):
         """ Transfer data from validator to window.

             The default implementation returns false, indicating that an error
             occurred. We simply return true, as we don't do any data transfer.
         """
         return false # Prevent wxDialog from complaining.

     def TransferFromWindow(self):
         """ Transfer data from window to validator.

             The default implementation returns false, indicating that an error
             occurred. We simply return true, as we don't do any data transfer.
         """
         return false # Prevent wxDialog from complaining.

#----------------------------------------------------------------------------

class TestApp(wxApp):
     def OnInit(self):
         testFrame = wxFrame(NULL, -1, "Test")

         menu = wxMenu()
         menu.Append(1001, "Open Dialog")

         menuBar = wxMenuBar()
         menuBar.Append(menu, "Test")

         EVT_MENU(self, 1001, self.doShowDialog)

         testFrame.SetMenuBar(menuBar)
         testFrame.Centre()
         testFrame.Show(TRUE)

         return true

     def doShowDialog(self, event):
         editor = EditTextObjectDialog(NULL, "Edit Text Object")
         result = editor.ShowModal()

#----------------------------------------------------------------------------

def main():
     app = TestApp(0)
     app.MainLoop()

if __name__ == "__main__":
     main()

=========================== End of Python Code =============================

I'm working on a simple drawing application which is supposed to
demonstrate all the main aspects of a typical wxPython app.

Unfortunately,

I'm stuck with on particular thing: I want to have a dialog box with a
validated wxTextCtrl in it...but I've had no luck getting it to work.

A couple problems...

1. In wxPython/windows.py in the wxPyValidator.__init__ method change

        self._setSelf(self, wxPyValidator, 0)

to

        self._setSelf(self, wxPyValidator, 1)

I'm not 100% sure that this will be required in 2.2.5 but it fixed a problem
in 2.3 in my current workspace.

2. Your Validate method is a bit flawed. The window passed to it is the
dialog, not the text control, so when you use wxPyTypeCast to change it to a
wxTextCtrl and then call GetValue it is actually calling the virtual method
of wxDialog that is in the same slot in it's vtable as GetValue is in
wxTextCtrl's. Looking at the demo I see that I was confused on this point
as well, but since it's in a panel with no buttons that would cause Validate
to be called like in the wxDialog then the error never showed up. The
window to be validated is available via the validator's GetWindow method.
I'll paste in the corrected validator code from your sample below.

BTW, I found this error because in 2.3.0 (almost ready for release) the
wxPyTypeCast is not neccessary and so I was able to see the real type of the
window referenced by the win parameter.

class TextObjectValidator(wxPyValidator):
     """ This validator is used to ensure that the user has entered
something
         into the text object editor dialog's text field.
     """
     def __init__(self):
         """ Standard constructor.
         """
         print "__init__"
         wxPyValidator.__init__(self)

     def Clone(self):
         """ Standard cloner.

             Note that every validator must implement the Clone() method.
         """
         print "Clone"
         return TextObjectValidator()

     def Validate(self, win):
         """ Validate the contents of the given text control.
         """
         print "Calling validate..."
         textCtrl = self.GetWindow()
         textCtrl = wxPyTypeCast(textCtrl, "wxTextCtrl")
         text = textCtrl.GetValue()

         if len(text) == 0:
             wxMessageBox("A text object must contain some text!", "Error")
             return false
         else:
             return true

     def TransferToWindow(self):
         """ Transfer data from validator to window.

             The default implementation returns false, indicating that an
error
             occurred. We simply return true, as we don't do any data
transfer.
         """
         print "TransferToWindow"
         return true # Prevent wxDialog from complaining.

     def TransferFromWindow(self):
         """ Transfer data from window to validator.

             The default implementation returns false, indicating that an
error
             occurred. We simply return true, as we don't do any data
transfer.
         """
         print "TransferFromWindow"
         return true # Prevent wxDialog from complaining.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

Hi Robin,

A couple problems...

1. In wxPython/windows.py in the wxPyValidator.__init__ method change

        self._setSelf(self, wxPyValidator, 0)

to

        self._setSelf(self, wxPyValidator, 1)

I'm not 100% sure that this will be required in 2.2.5 but it fixed a problem
in 2.3 in my current workspace.

Yup, that definitely fixed the problem with wxPython crashing. Question: is this fix going to be included in the 2.3 release of wxPython (which, I understand, will be out shortly)? I hope so, because I'd rather not have to tell the Getting Started Guide readers to patch their wxPython source before they can run the demo app!

2. Your Validate method is a bit flawed. The window passed to it is the
dialog, not the text control, so when you use wxPyTypeCast to change it to a
wxTextCtrl and then call GetValue it is actually calling the virtual method
of wxDialog that is in the same slot in it's vtable as GetValue is in
wxTextCtrl's. Looking at the demo I see that I was confused on this point
as well, but since it's in a panel with no buttons that would cause Validate
to be called like in the wxDialog then the error never showed up. The
window to be validated is available via the validator's GetWindow method.
I'll paste in the corrected validator code from your sample below.

Ahh -- thanks. I'd based my example on your demo, which is why I got confused...

My text object editor works perfectly now. Thanks!

  - Erik.

> self._setSelf(self, wxPyValidator, 1)
>
>I'm not 100% sure that this will be required in 2.2.5 but it fixed a

problem

>in 2.3 in my current workspace.

Yup, that definitely fixed the problem with wxPython crashing. Question:
is this fix going to be included in the 2.3 release of wxPython (which, I
understand, will be out shortly)?

Yep, I made the change this morning. It will go into CVS once the other
stuff I am working on gets finshed and tested.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

Hi Robin,

> Question: is this fix going to be included in the 2.3 release of
> wxPython (which, I understand, will be out shortly)?

Yep, I made the change this morning. It will go into CVS once the other
stuff I am working on gets finshed and tested.

Excellent!

Thanks,

  - Erik.