Changeset 933


Ignore:
Timestamp:
May 26, 2013 1:35:30 PM (9 years ago)
Author:
toby
Message:

Add megawidgets needed for CIF development

Location:
trunk
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/GSASIIgrid.py

    r927 r933  
    1212--------------------------------
    1313
    14 
    1514'''
    1615import wx
     
    1817import wx.wizard as wz
    1918import wx.aui
     19import wx.lib.scrolledpanel as wxscroll
    2020import time
    2121import cPickle
     
    3939import GSASIIconstrGUI as G2cnstG
    4040import GSASIIrestrGUI as G2restG
     41import GSASIIpy3 as G2py3
    4142
    4243# trig functions in degrees
     
    137138################################################################################
    138139       
     140class ValidatedTxtCtrl(wx.TextCtrl):
     141    '''Create a TextCtrl widget that uses a validator to prevent the
     142    entry of inappropriate characters and changes color to highlight
     143    when invalid input is supplied. As valid values are typed,
     144    they are placed into the dict or list where the initial value
     145    came from. The type of the initial value must be int, float or str;
     146    this type is preserved.
     147
     148    Float values can be entered in the TextCtrl as numbers or also
     149    as algebraic expressions using operators + - / \* () and \*\*,
     150    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
     151    as well as appreviations s, sin, c, cos, t, tan and sq.
     152
     153    :param wx.Panel parent: name of panel or frame that will be
     154      the parent to the TextCtrl
     155
     156    :param dict/list loc: the dict or list with the initial value to be
     157      placed in the TextCtrl
     158
     159    :param int/str key: the dict key or the list index for the value   
     160
     161    :param bool notBlank: if True (default) blank values are not allowed
     162      for str inputs.
     163     
     164    :param number min: Minimum allowed value. If None (default) the
     165      lower limit is unbounded
     166
     167    :param number max: Maximum allowed value. If None (default) the
     168      upper limit is unbounded
     169
     170    :param wx.Size size: an optional size parameter that dictates the
     171      size for the TextCtrl. None (the default) indicates that the
     172      default size should be used.
     173
     174    :param function OKcontrol: specifies a function or method that will be
     175      called when the input is validated. It is supplied with one argument
     176      which is False if the TextCtrl contains an invalid value and True if
     177      the value is valid. Note that this function should check all values
     178      in the dialog when True, since other entries might be invalid.
     179
     180    '''
     181    def __init__(self,parent,loc,key,notBlank=True,min=None,max=None,
     182                 size=None,OKcontrol=None):
     183        self.result = loc
     184        self.key = key
     185        self.OKcontrol=OKcontrol
     186        self.invalid = None
     187        val = loc[key]
     188        if isinstance(val,int):
     189            wx.TextCtrl.__init__(
     190                self,parent,wx.ID_ANY,str(val),
     191                validator=NumberValidator(int,result=loc,key=key,min=min,max=max,
     192                                          OKcontrol=OKcontrol)
     193                )
     194            self.invalid = not self.Validate()
     195        elif isinstance(val,int) or isinstance(val,float):
     196            wx.TextCtrl.__init__(
     197                self,parent,wx.ID_ANY,str(val),
     198                validator=NumberValidator(float,result=loc,key=key,min=min,max=max,
     199                                          OKcontrol=OKcontrol)
     200                )
     201            self.invalid = not self.Validate()
     202        elif isinstance(val,str):
     203            wx.TextCtrl.__init__(self,parent,wx.ID_ANY,str(val))
     204            if notBlank:
     205                self.Bind(wx.EVT_CHAR,self._onStringKey)
     206                self.ShowStringValidity() # test if valid input
     207            else:
     208                self.invalid = False
     209        else:
     210            raise Exception,"ValidatedTxtCtrl Unknown type "+str(type(val))
     211        if size: self.SetSize(size)
     212
     213    def _onStringKey(self,event):
     214        event.Skip()
     215        if self.invalid: # check for validity after processing the keystroke
     216            wx.CallAfter(self.ShowStringValidity,True) # was invalid
     217        else:
     218            wx.CallAfter(self.ShowStringValidity,False) # was valid
     219
     220    def ShowStringValidity(self,previousInvalid=None):
     221        '''Check if input is valid. Anytime the input is
     222        invalid, call self.OKcontrol (if defined) because it is fast.
     223        If valid, check for any other invalid entries only when
     224        changing from invalid to valid, since that is slower.
     225       
     226        :param bool previousInvalid: True if the TextCtrl contents were
     227          invalid prior to the current change.
     228         
     229        '''
     230        val = self.GetValue().strip()
     231        self.invalid = not val
     232        'Set the control colors to show invalid input'
     233        if self.invalid:
     234            self.SetForegroundColour("red")
     235            self.SetBackgroundColour("yellow")
     236            self.SetFocus()
     237            self.Refresh()
     238            if self.OKcontrol:
     239                self.OKcontrol(False)
     240        else: # valid input
     241            self.SetBackgroundColour(
     242                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
     243            self.SetForegroundColour("black")
     244            self.Refresh()
     245            self.result[self.key] = val
     246            if self.OKcontrol and previousInvalid:
     247                self.OKcontrol(True)
     248       
     249class NumberValidator(wx.PyValidator):
     250    '''A validator to be used with a TextCtrl to prevent
     251    entering characters other than digits, signs, and for float
     252    input, a period and exponents.
     253   
     254    The value is checked for validity after every keystroke
     255      If an invalid number is entered, the box is highlighted.
     256      If the number is valid, it is saved in result[key]
     257
     258    :param type typ: data type, int or float
     259
     260    :param bool positiveonly: used for typ=int. If True, only positive
     261      integers are allowed (default False)
     262
     263    :param number min: Minimum allowed value. If None (default) the
     264      lower limit is unbounded
     265
     266    :param number max: Maximum allowed value. If None (default) the
     267      upper limit is unbounded
     268     
     269    :param dict/list result: List or dict where value should be placed when valid
     270
     271    :param any key: key to use for result (int for list)
     272
     273    :param function OKcontrol: function or class method to control
     274      an OK button for a window.
     275      Ignored if None (default)
     276    '''
     277    def __init__(self, typ, positiveonly=False, min=None, max=None,
     278                 result=None, key=None, OKcontrol=None):
     279        'Create the validator'
     280        wx.PyValidator.__init__(self)
     281        self.typ = typ
     282        self.positiveonly = positiveonly
     283        self.min = min
     284        self.max = max
     285        self.result = result
     286        self.key = key
     287        self.OKcontrol = OKcontrol
     288        self.evaluated = False
     289        # When the mouse is moved away or the widget loses focus
     290        # display the last saved value
     291        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
     292        self.Bind(wx.EVT_KILL_FOCUS, self.OnLeave)
     293        if self.typ == int and self.positiveonly:
     294            self.validchars = '0123456789'
     295            self.Bind(wx.EVT_CHAR, self.OnChar)
     296        elif self.typ == int:
     297            self.validchars = '0123456789+-'
     298            self.Bind(wx.EVT_CHAR, self.OnChar)
     299        elif self.typ == float:
     300            # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations
     301            # also addition, subtraction, division, multiplication, exponentiation
     302            self.validchars = '0123456789.-+eE/cosindcqrtap()*'
     303            self.Bind(wx.EVT_CHAR, self.OnChar)
     304        else:
     305            self.validchars = None
     306    def Clone(self):
     307        'Create a copy of the validator, a strange, but required component'
     308        return NumberValidator(typ=self.typ,
     309                          positiveonly=self.positiveonly,
     310                          min=self.min, max=self.max,
     311                          result=self.result, key=self.key,
     312                          OKcontrol=self.OKcontrol)
     313        tc = self.GetWindow()
     314        tc.invalid = False # make sure the validity flag is defined in parent
     315    def TransferToWindow(self):
     316        'Needed by validator, strange, but required component'
     317        return True # Prevent wxDialog from complaining.
     318    def TransferFromWindow(self):
     319        'Needed by validator, strange, but required component'
     320        return True # Prevent wxDialog from complaining.
     321    def TestValid(self,tc):
     322        '''Check if the value is valid by casting the input string
     323        into the current type.
     324
     325        Set the invalid variable in the TextCtrl object accordingly.
     326
     327        If the value is valid, save it in the dict/list where
     328        the initial value was stored, if appropriate.
     329        '''
     330        tc.invalid = False # assume invalid
     331        try:
     332            val = self.typ(tc.GetValue())
     333        except (ValueError, SyntaxError) as e:
     334            if self.typ is float: # for float values, see if an expression can be
     335                # evaluated
     336                val = G2py3.FormulaEval(tc.GetValue())
     337                if val is None:
     338                    tc.invalid = True
     339                    return
     340                else:
     341                    self.evaluated = True
     342            else:
     343                tc.invalid = True
     344                return
     345        if self.max != None and self.typ == int:
     346            if val > self.max:
     347                tc.invalid = True
     348        if self.max != None and self.typ == int:
     349            if val < self.min:
     350                tc.invalid = True  # invalid
     351        if self.key is not None and self.result is not None and not tc.invalid:
     352            self.result[self.key] = val
     353
     354    def ShowValidity(self,tc):
     355        'Set the control colors to show invalid input'
     356        if tc.invalid:
     357            tc.SetForegroundColour("red")
     358            tc.SetBackgroundColour("yellow")
     359            tc.SetFocus()
     360            tc.Refresh()
     361            return False
     362        else: # valid input
     363            tc.SetBackgroundColour(
     364                wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
     365            tc.SetForegroundColour("black")
     366            tc.Refresh()
     367            return True
     368
     369    def OnLeave(self, event):
     370        '''Show the computed value when an expression is entered to the TextCtrl
     371        Make sure that the number fits by truncating decimal places and switching
     372        to scientific notation, as needed.
     373        Called on loss of focus.
     374        '''
     375        tc = self.GetWindow()
     376        if tc.invalid: return # don't substitute for an invalid expression
     377        if not self.evaluated: return # true when an expression is evaluated
     378        self.evaluated = False
     379        if self.result is not None: # retrieve the stored result
     380            val = self.result[self.key]
     381            tc.SetValue(G2py3.FormatValue(val))
     382
     383    def CheckInput(self,previousInvalid):
     384        '''called to test every change to the TextCtrl for validity and
     385        to change the appearance of the TextCtrl
     386
     387        Anytime the input is invalid, call self.OKcontrol
     388        (if defined) because it is fast.
     389        If valid, check for any other invalid entries only when
     390        changing from invalid to valid, since that is slower.
     391        '''
     392        tc = self.GetWindow()
     393        self.TestValid(tc)
     394        self.ShowValidity(tc)
     395        # if invalid and
     396        if tc.invalid and self.OKcontrol:
     397            self.OKcontrol(False)
     398        if not tc.invalid and self.OKcontrol and previousInvalid:
     399            self.OKcontrol(True)
     400
     401    def OnChar(self, event):
     402        '''Called each type a key is pressed
     403        ignores keys that are not allowed for int and float types
     404        '''
     405        key = event.GetKeyCode()
     406        if key == wx.WXK_RETURN:
     407            tc = self.GetWindow()
     408            if tc.invalid:
     409                self.CheckInput(True)
     410            else:
     411                self.CheckInput(False)
     412            return
     413        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed
     414            event.Skip()
     415            tc = self.GetWindow()
     416            if tc.invalid:
     417                wx.CallAfter(self.CheckInput,True)
     418            else:
     419                wx.CallAfter(self.CheckInput,False)
     420            return
     421        elif chr(key) in self.validchars: # valid char pressed?
     422            event.Skip()
     423            tc = self.GetWindow()
     424            if tc.invalid:
     425                wx.CallAfter(self.CheckInput,True)
     426            else:
     427                wx.CallAfter(self.CheckInput,False)
     428            return
     429        if not wx.Validator_IsSilent():
     430            wx.Bell()
     431        return  # Returning without calling event.Skip, which eats the keystroke
     432
     433################################################################################
     434class ScrolledMultiEditor(wx.Dialog):
     435    '''Define a window for editing a potentially large number of dict- or
     436    list-contained values with validation for each item. Edited values are
     437    automatically placed in their source location. If invalid entries
     438    are provided, the TextCtrl is turned yellow and the OK button is disabled.
     439
     440    The type for each TextCtrl validation is determined by the
     441    initial value of the entry (int, float or string).
     442    Float values can be entered in the TextCtrl as numbers or also
     443    as algebraic expressions using operators + - / \* () and \*\*,
     444    in addition pi, sind(), cosd(), tand(), and sqrt() can be used,
     445    as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq().
     446
     447    :param wx.Frame parent: name of parent window, or may be None
     448
     449    :param tuple dictlst: a list of dicts or lists containing values to edit
     450
     451    :param tuple elemlst: a list of keys for each item in a dictlst. Must have the
     452      same length as dictlst.
     453
     454    :param wx.Frame parent: name of parent window, or may be None
     455   
     456    :param tuple prelbl: a list of labels placed before the TextCtrl for each
     457      item (optional)
     458   
     459    :param tuple postlbl: a list of labels placed after the TextCtrl for each
     460      item (optional)
     461
     462    :param str title: a title to place in the frame of the dialog
     463
     464    :param str header: text to place at the top of the window. May contain
     465      new line characters.
     466
     467    :param wx.Size size: a size parameter that dictates the
     468      size for the scrolled region of the dialog. The default is
     469      (300,250).
     470
     471    :returns: the wx.Dialog created here. Use .ShowModal() to
     472   
     473    ''Example for use of ScrolledMultiEditor:''
     474
     475    ::
     476
     477        dlg = <pkg>.ScrolledMultiEditor(frame,dictlst,elemlst,prelbl,postlbl,
     478                                        header=header)
     479        if dlg.ShowModal() == wx.ID_OK:
     480             for d,k in zip(dictlst,elemlst):
     481                 print d[k]
     482
     483    ''Example definitions for dictlst and elemlst:''
     484
     485    ::
     486     
     487          dictlst = (dict1,list1,dict1,list1)
     488          elemlst = ('a', 1, 2, 3)
     489
     490      This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited.
     491   
     492    Note that these items must have int, float or str values assigned to
     493    them. The dialog will force these types to be retained. String values
     494    that are blank are marked as invalid.
     495    '''
     496   
     497    def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[],
     498                 title='Edit items',header='',size=(300,250)):
     499        if len(dictlst) != len(elemlst):
     500            raise Exception,"ScrolledMultiEditor error: len(dictlst) != len(elemlst) "+str(len(dictlst))+" != "+str(len(elemlst))
     501        wx.Dialog.__init__( # create dialog & sizer
     502            self,parent,wx.ID_ANY,title,
     503            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
     504        mainSizer = wx.BoxSizer(wx.VERTICAL)
     505        # ad a header if supplied
     506        if header:
     507            subSizer = wx.BoxSizer(wx.HORIZONTAL)
     508            subSizer.Add((-1,-1),1,wx.EXPAND)
     509            subSizer.Add(wx.StaticText(self,wx.ID_ANY,header))
     510            subSizer.Add((-1,-1),1,wx.EXPAND)
     511            mainSizer.Add(subSizer,0,wx.EXPAND,0)
     512        # make OK button now, because we will need it for validation
     513        self.OKbtn = wx.Button(self, wx.ID_OK)
     514        self.OKbtn.SetDefault()
     515        # create scrolled panel and sizer
     516        panel = wxscroll.ScrolledPanel(
     517            self, wx.ID_ANY,
     518            size=size,
     519            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
     520        subSizer = wx.FlexGridSizer(rows=len(dictlst),cols=3,hgap=2,vgap=2)
     521        self.ValidatedControlsList = [] # make list of TextCtrls
     522        for i,(d,k) in enumerate(zip(dictlst,elemlst)):
     523            if i >= len(prelbl): # label before TextCtrl, or put in a blank
     524                subSizer.Add((-1,-1))
     525            else:
     526                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i])))
     527            # create the validated TextCrtl, store it and add it to the sizer
     528            ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton)
     529            self.ValidatedControlsList.append(ctrl)
     530            subSizer.Add(ctrl)
     531            if i >= len(postlbl): # label after TextCtrl, or put in a blank
     532                subSizer.Add((-1,-1))
     533            else:
     534                subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(postlbl[i])))
     535        # finish up ScrolledPanel
     536        panel.SetSizer(subSizer)
     537        panel.SetAutoLayout(1)
     538        panel.SetupScrolling()
     539        mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1)
     540
     541        # Sizer for OK/Close buttons. N.B. using Close rather than Cancel because
     542        # Cancel would imply that the changes should be discarded. In fact
     543        # any valid changes are retained, unless one makes a copy of the
     544        # input dicts & lists and restores them.
     545        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
     546        btnsizer.Add(self.OKbtn)
     547        btn = wx.Button(self, wx.ID_CLOSE)
     548        btn.Bind(wx.EVT_BUTTON,self._onClose)
     549        btnsizer.Add(btn)
     550        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
     551        # size out the window. Set it to be enlarged but not made smaller
     552        self.SetSizer(mainSizer)
     553        mainSizer.Fit(self)
     554        self.SetMinSize(self.GetSize())
     555
     556    def _onClose(self,event):
     557        'Close the window'
     558        self.EndModal(wx.ID_CANCEL)
     559       
     560    def ControlOKButton(self,setvalue):
     561        '''Enable or Disable the OK button for the dialog. Note that this is
     562        passed into the ValidatedTxtCtrl for use by validators.
     563
     564        :param bool setvalue: if True, all entries in the dialog are
     565          checked for validity. if False then the OK button is disabled.
     566
     567        '''
     568        if setvalue: # turn button on, do only if all controls show as valid
     569            for ctrl in self.ValidatedControlsList:
     570                if ctrl.invalid:
     571                    self.OKbtn.Disable()
     572                    return
     573            else:
     574                self.OKbtn.Enable()
     575        else:
     576            self.OKbtn.Disable()
     577
     578################################################################################
    139579class SymOpDialog(wx.Dialog):
    140580    '''Class to select a symmetry operator
     
    460900        parent.Raise()
    461901        self.EndModal(wx.ID_CANCEL)
    462        
     902
     903################################################################################
     904class SingleStringDialog(wx.Dialog):
     905    '''Dialog to obtain a single string value from user
     906   
     907    :param wx.Frame parent: name of parent frame
     908    :param str title: title string for dialog
     909    :param str prompt: string to tell use what they are inputting
     910    :param str value: default input value, if any
     911    '''
     912    def __init__(self,parent,title,prompt,value='',size=(200,-1)):
     913        wx.Dialog.__init__(self,parent,wx.ID_ANY,title,
     914            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
     915        self.value = value
     916        self.prompt = prompt
     917        self.CenterOnParent()
     918        self.panel = wx.Panel(self)
     919        mainSizer = wx.BoxSizer(wx.VERTICAL)
     920        mainSizer.Add(wx.StaticText(self.panel,-1,self.prompt),0,wx.ALIGN_CENTER)
     921        self.valItem = wx.TextCtrl(self.panel,-1,value=str(self.value),size=size)
     922        mainSizer.Add(self.valItem,0,wx.ALIGN_CENTER)
     923        btnsizer = wx.StdDialogButtonSizer()
     924        OKbtn = wx.Button(self.panel, wx.ID_OK)
     925        OKbtn.SetDefault()
     926        btnsizer.AddButton(OKbtn)
     927        btn = wx.Button(self.panel, wx.ID_CANCEL)
     928        btnsizer.AddButton(btn)
     929        btnsizer.Realize()
     930        mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER)
     931        self.panel.SetSizer(mainSizer)
     932        self.panel.Fit()
     933        self.Fit()
     934
     935    def Show(self):
     936        '''Use this method after creating the dialog to post it
     937        :returns: True if the user pressed OK; False if the User pressed Cancel
     938        '''
     939        if self.ShowModal() == wx.ID_OK:
     940            self.value = self.valItem.GetValue()
     941            return True
     942        else:
     943            return False
     944
     945    def GetValue(self):
     946        '''Use this method to get the value entered by the user
     947        :returns: string entered by user
     948        '''
     949        return self.value
     950
     951################################################################################
     952def ItemSelector(ChoiceList, ParentFrame=None,
     953                 title='Select an item',
     954                 size=None, header='Item Selector',
     955                 useCancel=True):
     956        ''' Provide a wx dialog to select a single item from list of choices
     957
     958        :param list ChoiceList: a list of choices where one will be selected
     959        :param wx.Frame ParentFrame: Name of parent frame (default None)
     960        :param str title: heading above list of choices (default 'Select an item')
     961        :param wx.Size size: Size for dialog to be created (default None -- size as needed)
     962        :param str header: Title to place on window frame (default 'Item Selector')
     963        :param bool useCancel: If True (default) both the OK and Cancel buttons are offered
     964
     965        :returns: the selection index or None
     966        '''
     967        if useCancel:
     968            dlg = wx.SingleChoiceDialog(
     969                ParentFrame,title, header, ChoiceList)
     970        else:
     971            dlg = wx.SingleChoiceDialog(
     972                ParentFrame,title, header,ChoiceList,
     973                style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
     974        if size: dlg.SetSize(size)
     975        if dlg.ShowModal() == wx.ID_OK:
     976            sel = dlg.GetSelection()
     977            return sel
     978        else:
     979            return None
     980        dlg.Destroy()
     981
     982################################################################################
    463983class GridFractionEditor(wg.PyGridCellEditor):
    464984    '''A grid cell editor class that allows entry of values as fractions as well
     
    5461066            evt.Skip()
    5471067
     1068################################################################################
    5481069class MyHelp(wx.Menu):
    5491070    '''
     
    7041225        return
    7051226
     1227################################################################################
    7061228class AddHelp(wx.Menu):
    7071229    '''This class a single entry for the help menu (used on the Mac only):
     
    7281250        ShowHelp(self.HelpById,self.frame)
    7291251
     1252################################################################################
    7301253class MyHtmlPanel(wx.Panel):
    7311254    '''Defines a panel to display Help information'''
     
    7851308            self.GetOpenedPageTitle())
    7861309
     1310################################################################################
    7871311class DataFrame(wx.Frame):
    7881312    '''Create the dataframe window and all the entries in menus.
     
    23962920    sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
    23972921
     2922if __name__ == '__main__':
     2923    # test ScrolledMultiEditor
     2924    app = wx.PySimpleApp()
     2925    frm = wx.Frame(None) # create a frame
     2926    frm.Show(True)
     2927    Data1 = {
     2928        'Order':0,
     2929        'omega':'string',
     2930        'chi':2.0,
     2931        'phi':'',
     2932        }
     2933    elemlst = sorted(Data1.keys())
     2934    postlbl = sorted(Data1.keys())
     2935    dictlst = len(elemlst)*[Data1,]
     2936
     2937    Data2 = list(range(100))
     2938    elemlst += range(2,60)
     2939    postlbl += range(2,60)
     2940    dictlst += len(range(2,60))*[Data2,]
     2941
     2942    prelbl = range(len(elemlst))
     2943    postlbl[1] = "a very long label for the 2nd item to force a horiz. scrollbar"
     2944    header="""This is a longer\nmultiline and perhaps silly header"""
     2945    dlg = ScrolledMultiEditor(frm,dictlst,elemlst,prelbl,postlbl,
     2946                                    header=header)
     2947    print Data1
     2948    if dlg.ShowModal() == wx.ID_OK:
     2949        for d,k in zip(dictlst,elemlst):
     2950            print k,d[k]
     2951    #app.MainLoop()
Note: See TracChangeset for help on using the changeset viewer.