RMWChaos 0 Newbie Poster

Maybe this is a bit too broad a question and too much code for this forum, but I'll give it a shot...

OS: Win7x64 6.1.7601
py: 2.7.2 (default, Jun 12 2011, 14:24:46) [MSC v.1500 64 bit (AMD64)]
wx: 2.8-msw-unicode

My question is more of a general nature than in regards to a specific problem to resolve:

Am I making this more difficult than it needs to be, and is there a better way to handle a combination of navigation and validation events?

After searching far and wide for examples of code to manage both navigation between controls and validation of user input, I finally cobled together the following ... and it works exactly as designed, surprisingly enough. I'm just not sure if I'm doing things the "hard way" and if there is a simpler, more elegant way of handling what I'm trying to accomplish.

I set out with the following criteria:

Navigation...

  1. NavKeyEvt must ONLY move between input controls (TextCtrl, ComboBox, etc.), not non-input action controls (Button, CheckBox, etc.)
  2. Mouse click and SetFocus() events should be handled in a similar fashion to NavKeyEvt in that focus is always returned to an input control after completing event handling of non-input controls
  3. Navigation can be:
    • bounded: forward nav stops at last input control; backward nav stops at first input control
    • unbounded: forward nav from last input control moves to first input control; backward nav from first input control moves to last input control
  4. Input control can be:
    • required: cannot navigate away from input control until a valid value is entered; control background color set according to status of value; bell sounds on attempt to nav away from control with invalid value
    • not required: can navigate away from input control if value is empty, but cannot navigate away if invalid value is entered, and background color is set according to status of value

Valdation...

  1. Character validation must occur on each keypress to allow user to enter only characters that are valid for the particular input control
  2. Value validation must occur on each navigation event (NavKeyEvt, MouseEvt, or SetFocus()) if value has changed or not been previously validated
  3. TextCtrl background color must change depending upon validation state of input:
    • sysColor: empty/not validated/not required/not focused
    • pink: empty/invalid/required/not focused
    • yellow: empty/not validated/focused
    • green: valid (regardless of focus or requirement)

I've redacted the code as much as possible to show only the relevant sections of validation and navigation, but even this is getting pretty long. Sorry about that.

# IMPORT STATEMENTS ------------------------------------------------------------
import os, sys
import wx
import re

# CLASS DEFINITIONS ------------------------------------------------------------
class InValidator(wx.PyValidator):
    """ Validates data from input fields """
    def __init__ (self):
        wx.PyValidator.__init__(self)
        self.Bind(wx.EVT_CHAR, self.onChar)

        # Validation types...
        self.FILE = 0
        self.PATH = 1

        # Define valid character regex...
        self.valid_file_path = re.compile(r'[^*?"<>|]')   # None of these chars

        # Define compiled, verbose regex for field value validation...
        # File validation...
        # Example: c:\test\test.txt
        self.valid_file = re.compile(r"""
            ^                  # Search at beginning of string literal
            [a-z]              # Begins with drive letter a-z
            {1}                # Only 1 occurrence of this value
            :                  # Followed by a ':'
            {1}                # Only 1 occurrence of this value
            (/|\\)             # Followed by a '\' or '/'
            {1}                # Only 1 occurrence of this value
            [^*:?"<>|]         # Followed by any except special characters
            +                  # 1 or more occurrences of these values
            (?<!\\)            # '\' must not occur right before ...
            (\.)               # ... a period '.' (dot)...
            {1}                # Only 1 occurrence of this value
            [^*:?"<>|/\\]      # (*) Followed by any except special characters
            +                  # (*) 1 or more occurrences of these values
            # txt              # Uncomment and change to specify file extension
            # {1}              # Uncomment if specifying a file extension
            $                  # ... at the end of the string literal
                               # Case is ignored ...
            """, re.VERBOSE | re.IGNORECASE)

        # If a specific valid_file extention is required, comment out the two
        #   lines with a (*) before the comments, uncomment the two lines below
        #   those, and change 'txt' to the desired file extension.

        # Path validation...
        # Example: c:\test\test
        self.valid_path =  re.compile(r"""
            ^                  # Search at beginning of string literal
            [a-z]              # Begins with drive letter a-z
            {1}                # Only 1 occurrence of this value
            :                  # Followed by a ':'
            {1}                # Only 1 occurrence of this value
            (/|\\)             # Followed by a '\' or '/'
            {1}                # Only 1 occurrence of this value
            [^*:?"<>|]         # Followed by any except special characters
            *                  # 0 or more occurrences of these values
            $                  # ... at the end of the string literal
                               # Case is ignored ...
            """, re.VERBOSE | re.IGNORECASE)

    def Clone (self):
        """ Required Validator method """
        return self.__class__()

    def Validate (self, window):
        """ Validate entire control input """
        valid = False

        # Find the owner of self.curFocus, self.ctrlList, self.inputList, etc...
        owner = window.GetTopLevelParent()
        control = owner.oldCtrl
        value = control.GetValue()
        name = control.GetName()
        index = owner.ctrlNames.index(name)
        flag = owner.inputList[index][3]

        if len(value) == 0:
            # Update statusWin with "Value is required"
            pass

        elif flag == self.FILE and self.valid_file.match(value):
            if self._isFormatted(value, flag):
                valid = True

        elif flag == self.PATH and self.valid_path.match(value):
            # Check that path exists, ask to create if not
            if self._isFormatted(value, flag):
                valid = True

        return valid
    # End Validate() method...

    def onChar (self, event):
        """ Validate control character input """
        key = event.GetKeyCode()
        control = event.GetEventObject()
        name = control.GetName()
        owner = control.GetTopLevelParent()
        self.oldCtrl = control
        index = owner.ctrlNames.index(name)
        flag = owner.inputList[index][3]
        valid = False

        # Define edit function key presses...
        editKeys = (key == wx.WXK_DELETE
                 or key == wx.WXK_NUMPAD_DELETE
                 or key == wx.WXK_BACK)

        # If MacOS platform...
        if 'wxMac' in wx.PlatformInfo:
            if event.CmdDown() and key == ord('c'):
                key = WXK_CTRL_C
            elif event.CmdDown() and key == ord('v'):
                key = WXK_CTRL_V

        try:
            # Attempt to validate against regex...
            char = chr(key)
            valid = ((flag == self.FILE and self.valid_file_path.search(char))
                  or (flag == self.PATH and self.valid_file_path.search(char)))

            # If valid, allow character in control and reset to not validated...
            if valid or editKeys:
                owner.inputList[index][2] = False
                control.SetBackgroundColour('Yellow')
                control.Refresh()
                event.Skip()

            # If not valid character, do not allow and alert...
            elif not wx.Validator_IsSilent():
                wx.Bell()

        except:
            # Key is < 32 or > 255...
            event.Skip()

        return
    # End onChar() method

    def TransferToWindow (self):
        return True
    # End TransferToWindow() method

    def TransferFromWindow (self):
        return True
    # End TransferFromWindow() method

    def _isFormatted (self, value, flag):
        """
        Check if value is formatted properly.
        Modify if/else code blocks to perform more complex validation such as
            checking if a file is in the correct format.
        """

        if flag == self.FILE:
            valid = os.path.isfile(value)

        elif flag == self.PATH:
            path = os.path.realpath(value)
            valid = os.path.isdir(path)

        else:
            valid = False

        return valid
    # End _isFormatted() private method
# End InValidator class


class genControl:
    """ Factory function to create controls based on user input. """
    def __init__ (self, parent, *args, **kwargs):
        # Default Method parameters...
        self.NAME = kwargs.get('NAME', 'Enter Name')
        self.VER = kwargs.get('VER', 'Enter version')
        self.COPY = kwargs.get('COPY', 'Enter copyright')
        self.ABOUT = kwargs.get('ABOUT', 'Enter about statement')
        self.URL = kwargs.get('URL', 'Enter URL')
        self.DEV = kwargs.get('DEV', 'Enter developer name(s)')

        self.navBound = kwargs.get('NAV_BOUND', False)
        self.defValid = kwargs.get('defValid', r'^[^*\\/:?"<>|]+$')
        self.defFormat = kwargs.get('defFormat', r'_, <, >, V, S')
        self.defExclude = kwargs.get('defExclude', r':*?"<>|')
        self.defInclude = kwargs.get('defInclude', r'')

        self.newDir = kwargs.get('newDir', False)
        self.startDir = kwargs.get('startDir', '.')
        self.fileMask = kwargs.get('fileMask', '*.*')
        self.fileMode = kwargs.get('fileMode', wx.OPEN)
        self.fileTarget = kwargs.get('fileTarget', '')
        self.pathTarget = kwargs.get('pathTarget', '')
        self.openFolder = kwargs.get('openFolder', False)

        # Control background colors...
        self.valBGC = kwargs.get('valBGC', 'Green')        # Valid
        self.selBGC = kwargs.get('selBGC', 'Yellow')       # Selected, not valid
        self.invBGC = kwargs.get('invBGC', 'Pink')         # Invalid
        self.sysBGC = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)

        # Default Method variables...
        self.ctrlList = []             # List of input control references
        self.ctrlNames = []            # List of input control names
        self.inputList = []            # List of input control parameter lists
                                       # [name, value, valid, valType, required]

        # Control types...
        self.TEXTCONTROL = 0           # wx.TextCtrl
        self.STATICTEXT = 1            # wx.StaticText
        self.BUTTON = 2                # wx.Button
        self.CHECKBOX = 3              # wx.CheckBox

        # Validation types...
        self.FILE = 0                  # Use valid_file regex
        self.PATH = 1                  # Use valid_path regex

        # inputList indices (self.inputList[INTx])...
        self.NAME = 0                  # STRING, control name
        self.VALUE = 1                 # STRING, INT, FLOAT, last value
        self.VALID = 2                 # BOOL, value is validated
        self.TYPE = 3                  # STRING, validation type to use
        self.REQ = 4                   # BOOL, value is required

    # BUILD CONTROL METHOD -----------------------------------------------------
    def buildControl(self, parent, ctrlType, *args, **kwargs):
        """ Build control based on 'controlType' definition. """
        # buildControl Method specific params...
        tip = kwargs.get('tip', 'Enter value')
        events = kwargs.get('events', [])
        required = kwargs.get('required', False)
        valType = kwargs.get('valType', self.FILE)

        # Standard wxpython control params...
        id = kwargs.get('id', wx.ID_ANY)
        value = kwargs.get('value', '')
        pos = kwargs.get('pos', wx.DefaultPosition)
        size = kwargs.get('size', wx.DefaultSize)
        style = kwargs.get('style', wx.TE_PROCESS_TAB)
        validator = kwargs.get('validator', wx.DefaultValidator)
        name = kwargs.get('name', 'ControlName')

        if ctrlType == self.STATICTEXT:
            control = wx.StaticText(parent, id, value, pos, size, style, name)

        elif ctrlType == self.TEXTCONTROL:
            control = wx.TextCtrl(parent, id, value, pos, size, style,
                                  validator, name)

        elif ctrlType == self.BUTTON:
            style = wx.CENTRE
            ctrlEvent = wx.EVT_BUTTON
            control = wx.Button(parent, id, value, pos, size, style,
                                validator, name)

        elif ctrlType == self.CHECKBOX:
            ctrlEvent = wx.EVT_CHECKBOX
            control = wx.CheckBox(parent, id, value, pos, size, style,
                                  validator, name)

        # Set control tool tip...
        control.SetToolTipString(tip)

        # Create ctrlNames, ctrlList, and inputList for input fields...
        if ctrlType == self.TEXTCONTROL:
            # Append to ctrlNames, ctrlList, and inputList...
            valid = False
            ctrlArgs = [name, value, valid, valType, required]
            self.ctrlNames.append(name)
            self.ctrlList.append(control)
            self.inputList.append(ctrlArgs)

        # Bind events to button and checkbox controls...
        elif ctrlType == self.BUTTON or ctrlType == self.CHECKBOX:
            for event in events:
                control.Bind(ctrlEvent, event)

        # Bind common events to all controls...
        control.Bind(wx.EVT_SET_FOCUS, self.onSetFocus)
        control.Bind(wx.EVT_RIGHT_DOWN, self.onRtBtn)
        control.Bind(wx.EVT_LEFT_DOWN, self.onLtBtn)
        control.Bind(wx.EVT_NAVIGATION_KEY, self.onNavKey)

        return control
    # End buildControl() method

    # ON EVENT METHODS ---------------------------------------------------------
    def onSetFocus (self, event):
        """ When focus is gained on new control... """
        newCtrl = event.GetEventObject()

        try:
            # Check if app has just been started...
            self.oldCtrl

        except:
            # App just started, set self.oldCtrl to first text control...
            self.oldCtrl = self.ctrlList[0]

        try:
            # Check if newCtrl is a text control...
            index = self.ctrlList.index(newCtrl)
            valid = self.inputList[index][self.VALID]

            if not valid:
                # Set newCtrl color to yellow...
                newCtrl.SetBackgroundColour(self.selBGC)

        except:
            # newCtrl is not a text control, skip event...
            event.Skip()
    # End onSetFocus() method

    def onRtBtn (self, event):
        """ When user clicks right mouse button... """
        control = event.GetEventObject()
        title = 'Tool Tip'
        message = control.GetToolTip().GetTip()
        style = wx.OK
        dialog = wx.MessageDialog(self, message, title, style)
        result = dialog.ShowModal()
        if result == wx.ID_OK:
            dialog.Destroy()
    # End onRtBtn() method

    def onLtBtn (self, event):
        """ When user clicks left mouse button... """
        newCtrl = event.GetEventObject()
        oldCtrl = self.FindFocus()
        #oldCtrl = self.oldCtrl
        oldFocus = oldCtrl.GetName()
        newFocus = newCtrl.GetName()
        if not oldFocus == newFocus:
            self.onNavigate(event, oldFocus, newFocus)
    # End onLtBtn() method

    def onNavKey (self, event):
        """ When user clicks TAB, SHIFT+TAB, or RETURN key... """
        navDir = event.GetDirection()
        control = self.FindFocus()
        oldFocus = control.GetName()
        index = self.ctrlNames.index(oldFocus)

        # ctrlNames indices...
        firstIdx = 0
        lastIdx = len(self.ctrlNames) - 1

        # TAB key pressed...
        if navDir == event.IsForward:
            index += 1
        # SHIFT+TAB keys pressed...
        elif navDir == event.IsBackward:
            index -= 1
        # Ignore all other nav keys (ALT+TAB, etc.)...
        else:
            return

        # If navigation is bound, stops at firstIdx and lastIdx...
        if self.navBound and (index > lastIdx or index < firstIdx):
            return

        # If navigation is not bound, loops between firstIdx and lastIdx...
        else:
            # Move index from lastIdx to firstIdx (forward nav)...
            if index > lastIdx:
                index = firstIdx
            # Move index from firstIdx to lastIdx (backwards nav)...
            elif index < firstIdx:
                index = lastIdx

        newFocus = self.ctrlNames[index]
        self.onNavigate(event, oldFocus, newFocus)
    # End onNavKey() method

    def onNavigate (self, event, oldFocus, newFocus):
        """ When user navigates to new control... """
        # ctrlNames indices...
        firstIdx = 0
        lastIdx = len(self.ctrlNames) - 1

        try:
            # If oldCtrl is a TextCtrl, get its values...
            oldCtrl = self.FindWindowByName(oldFocus)
            oldIdx = self.ctrlNames.index(oldFocus)
            newVal = oldCtrl.GetValue()
            oldVal = self.inputList[oldIdx][self.VALUE]
            oldValid = self.inputList[oldIdx][self.VALID]
            required = self.inputList[oldIdx][self.REQ]

        except:
            # BUGFIX: If oldCtrl is a non-TextCtrl, escape navigation logic...
            # Only occurs if there is a bug in non-TextCtrl event handling...
            event.Skip()
            return

        try:
            # Is newFocus in control list...
            newIdx = self.ctrlNames.index(newFocus)
            newCtrl = self.ctrlList[newIdx]

            """
            # If navigating backwards...
            if ((oldIdx > newIdx            # From later to earlier control...
                 and oldIdx != firstIdx     # and oldCtrl is not first ctrl...
                 and oldIdx != lastIdx      # and oldCtrl is not last ctrl...
                 and not oldValid)          # and oldVal is not yet validated
                or (oldIdx == lastIdx       # -or- oldCtrl is first control...
                 and newIdx < lastIdx       # and newCtrl is later control...
                 and (not oldValid          # and oldVal is not yet validated...
                 or not required))):        # or oldCtrl is not required
                oldCtrl.SetBackgroundColour(self.sysBGC)
                oldCtrl.Refresh()
            """

            isValid = self._isValid(oldCtrl, newFocus)

            if not isValid and (newVal or required):
                # If oldVal is not valid and a value is entered or required...
                return

            #elif isValid or (not isValid and not required):
            else:
                # If oldVal is valid, or is not valid and is not required...
                self.oldCtrl = newCtrl
                newCtrl.SetFocus()

        except:
            # If not in control list...
            self._isValid(oldCtrl, newFocus)
            event.Skip()
    # End onNavigate() method

    def onBrowseFile (self, event):
        """ Going to browse for file... """
        control = self.FindWindowByName(self.fileTarget)

        # If previous controls are valid or not required...
        if self._preValid(control):
            control.SetFocus()
            current = control.GetValue()
            directory = os.path.split(current)

            if os.path.isdir(current):
                directory = current
                current = ''
            elif directory and os.path.isdir(directory[0]):
                current = directory[1]
                directory = directory[0]
            else:
                directory = self.startDir
                current = ''

            tip = 'Select a file...'
            dialog = wx.FileDialog(self, tip, directory, current,
                                   self.fileMask, self.fileMode)
            result = dialog.ShowModal()
            dialog.Destroy()
            if result == wx.ID_OK:
                control.SetValue(dialog.GetPath())
    # End onBrowseFile() method

    def onBrowseDir (self, event):
        """ Going to browse for directory... """
        control = self.FindWindowByName(self.pathTarget)

        # If previous controls are valid or not required...
        if self._preValid(control):
            control.SetFocus()
            style = 0
            if not self.newDir:
                style |= wx.DD_DIR_MUST_EXIST
            self.changeCallback = None
            message = 'Select a Folder'
            dialog = wx.DirDialog(self, message, self.startDir, style)
            result = dialog.ShowModal()
            dialog.Destroy()
            if result == wx.ID_OK:
                control.SetValue(dialog.GetPath())
    # End onBrowseDir() method

    def onCheck (self, event):
        """ Checkbox selected """
        self.oldCtrl.SetFocus()
        checkBox = event.GetEventObject()
        if checkBox.IsChecked():
            self.openFolder = True
        else:
            self.openFolder = False
    # End onCheck() method

    def onCloseWindow (self, event = None):
        """ On closing the app window (Frame) """
        win = wx.Window_FindFocus()
        if win != None:
            # Note: you really have to use wx.wxEVT_KILL_FOCUS
            #     instead of wx.EVT_KILL_FOCUS here:
            win.Disconnect(-1, -1, wx.wxEVT_KILL_FOCUS)
        self.Destroy()
    # End onCloseWindow() method

    # PRIVATE METHODS ----------------------------------------------------------
    def _isValid (self, control, newFocus = None):
        """ Check if control is valid """
        newVal = control.GetValue()
        index = self.ctrlList.index(control)
        oldVal = self.inputList[index][self.VALUE]
        valid = self.inputList[index][self.VALID]
        required = self.inputList[index][self.REQ]

        # If value has changed or not been validated...
        if newVal != oldVal or not valid:
            validate = control.GetParent().Validate()
 
            # Check if new value is valid...
            if validate:
                self.inputList[index][self.VALUE] = newVal
                self.inputList[index][self.VALID] = True
                valid = True

            # New value is not valid...
            else:
                self.inputList[index][self.VALID] = False
                valid = False

        # If newVal is valid...
        if valid:
            control.SetBackgroundColour(self.valBGC)

        # If newVal is not valid and not required...
        elif not newVal and not required:
            control.SetBackgroundColour(self.sysBGC)

        # If newVal is not valid and required...
        else:
            control.SetBackgroundColour(self.invBGC)
            if newFocus in self.ctrlNames:
                wx.Bell()

        control.Refresh()
        return valid
    # End _isValid() private method

    def _preValid (self, control):
        """ Check if previous input control is required and invalid """
        newFocus = control.GetName()
        index = self.ctrlList.index(control)
        valid = True

        # Check validation of all previous input controls...
        for oldCtrl in self.ctrlList[:index]:
            oldIdx = self.ctrlList.index(oldCtrl)
            required = self.inputList[oldIdx][self.REQ]

            # If oldCtrl value is required and invalid, cancel event...
            if not self._isValid(oldCtrl, newFocus) and required:
                oldCtrl.SetFocus()
                valid = False
                break
        return valid
    #End _preValid() private method
# END genControl CLASS ---------------------------------------------------------

class myTestFrame(wx.Frame, genControl):
    """ Create GUI for pyARCsquared app """
    def __init__ (self, parent, *args, **kwargs):
        wx.Frame.__init__(self, parent, wx.ID_ANY, 'myTestFrame')
        genControl.__init__(self, parent)
        self.Bind(wx.EVT_CLOSE, self.onCloseWindow)

        self.fileTarget = 'inFileInput'
        self.pathTarget = 'outPathInput'

        # Build panel and border sizer...
        myTestPanel = wx.Panel(self)
        myTestBorder = wx.BoxSizer(wx.VERTICAL)
        myTestBorder.Add(myTestPanel, 0, wx.EXPAND)

        inFileLabel = self.buildControl(
            myTestPanel,
            self.STATICTEXT,
            value = 'FILE NAME:',
            size = (85, -1),
            style = wx.ALIGN_RIGHT,
            name = 'inFileLabel',
            tip = 'Enter or browse for file'
            )

        inFileInput = self.buildControl(
            myTestPanel,
            self.TEXTCONTROL,
            required = True,
            size = (295, -1),
            style = wx.ALIGN_LEFT,
            validator = InValidator(),
            valType = self.FILE,
            name = 'inFileInput',
            tip = 'Type input filename or click Browse to select a file',
            )

        inFileBrowse = self.buildControl(
            myTestPanel,
            self.BUTTON,
            value = 'Browse File',
            name = 'inFileBrowse',
            tip = 'Browse to select an input file',
            size = (80, -1),
            events = [self.onBrowseFile]
            )

        # Build outPath controls...
        outPathLabel = self.buildControl(
            myTestPanel,
            self.STATICTEXT,
            value = 'OUTPUT PATH:',
            size = (85, -1),
            style = wx.ALIGN_RIGHT,
            name = 'outPathLabel',
            tip = 'Enter or browse for output path'
            )

        outPathInput = self.buildControl(
            myTestPanel,
            self.TEXTCONTROL,
            size = (295, -1),
            style = wx.ALIGN_LEFT,
            validator = InValidator(),
            valType = self.PATH,
            name = 'outPathInput',
            tip = 'Type output path or click Browse to select a folder',
            )

        outPathBrowse = self.buildControl(
            myTestPanel,
            self.BUTTON,
            value = 'Browse Path',
            name = 'outPathBrowse',
            tip = 'Browse to select an output folder',
            size = (80, -1),
            events = [self.onBrowseDir]
            )

        inFileGrid = wx.FlexGridSizer(1, 3, 5, 5)
        outPathGrid = wx.FlexGridSizer(1, 3, 5, 5)

        inFileGrid.AddMany([
            (inFileLabel, 0,
                wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL),
            (inFileInput, 0,
                wx.EXPAND | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL),
            (inFileBrowse)
            ])

        outPathGrid.AddMany([
            (outPathLabel, 0,
                wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL),
            (outPathInput, 0,
                wx.EXPAND | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL),
            (outPathBrowse)
            ])

        myBoxSizer = wx.BoxSizer(wx.VERTICAL)
        myBoxSizer.Add(inFileGrid, 0, wx.EXPAND | wx.ALL, 5)
        myBoxSizer.Add(outPathGrid, 0, wx.EXPAND | wx.ALL, 5)
        myTestPanel.SetSizerAndFit(myBoxSizer)

        # Layout Frame Panel...
        self.SetAutoLayout(True)
        self.SetSizerAndFit(myTestBorder)
        self.Layout()
# End myTestFrame class


class myTestApp (wx.App):
    """ The wx.App for myTestApp """
    def OnInit (self):
        wx.InitAllImageHandlers()
        frame = myTestFrame(None)
        frame.Center(True)
        frame.Show(True)
        self.SetTopWindow(frame)
        return True
# End myTestApp class


# Run myTestApp...
if __name__ == '__main__':
        app = myTestApp(False)
        wx.SystemOptions.SetOptionInt('msw.window.no-clip-children', 1)
        app.MainLoop()