wxPython and how to return text entry to main program?

Steve Holden steve at holdenweb.com
Thu Apr 19 19:01:53 EDT 2007


Tyler wrote:
> Hello All:
> 
> I am currently working on a project to create an FEM model for school.
> I was thinking about using wxPython to gather the 12 input variables
> from the user, then, after pressing the "Run" button, the GUI would
> close, and the 12 input variables would then be available for the rest
> of the program.
> 
> So far, what I have been able to do is mostly a reverse engineering
> job to get the frame to look right and return the text variable to a
> dialog box.
> 
> I have read about a "redirect" that could be used to send the values
> to a file. But, then I would have to open the file and read in the
> data from there. This seems crude and lacking elegance.
> 
> Any help on how to get the program to output the data back to the main
> python program and close when I press submit? My apologies if this is
> something of a simple question, but I have only started in on wxPython
> about a week ago, and Python this term.
> 
> The codes I am using are below.
> 
> Any help (or suggested reading material) is greatly appreciated.

There are two basic ways to do this. The first is to pass in something
that can store the values collected by the window. The second is to have
the values stored as attributes of the dialog and then extract them
after the user has finished interacting with the dialog but before you
Destroy() it.

See comments in the code. I have actually appended a version that works. 
Maybe not the prettiest way to achieve what you want but it works.
> 
> Cheers,
> 
> t.
> 
> 
> MY MAIN PROGRAM
> 
> #!/usr/bin/env python
> import femGUI
> app = femGUI.MyApp(False)
> dlg = femGUI.FemInput()

So this is where you create your FemInput object. Here's where you could
pass it an object to receive the data values.

Assuming you want to store them as named attributes, about the simplest
thing you can use is an instance of some object. Here's an example in
the interactive interpreter:

  >>> class O: pass
  ...
  >>> myo = O()
  >>> myo.a = "hello"
  >>> myo.b = "world"
  >>> myo.a
'hello'
  >>>

So you could change that last line to

class Data: pass
data = Data()
app = femGUI.FemInput(data)

Of course this will mean edits below.

> dlg.Destroy()

It seems a bit strange to be destroying your dialog before you've
actually called the MainLoop to start doing the windowing. I have to
take your word for it that this works ... it's almost certainly the
reason why you are having problems getting data out of the window. Or
maybe not.

I see when you run the program the window doesn't disappear when you
terminate the dialog. That's probably because (unless you want to do
more windowing stuff) you probably want to use ShowModal instead. This
will terminate the dialog and have it return to the caller when it's
finished.

Hmm, the whole structure's a little bit loopy ... and a FemInput window
has to be a dialog if you're going to use ShowModal() on it anyway ... 
that's not good. It means the top-level frame has to Destroy() itself to 
terminate the main loop and let you continue with the non-graphical portion.

> app.MainLoop()
> 
> # Then do something with inputs here....
> 
The problem at the moment is by the time you are ready to "do something 
with the inputs" you have terminated your graphical environment - it is, 
to coin a phrase, an *ex*-graphical environemnt, and it's too late to 
poke about in it.

OK, here's a new main program for you. To make the "lines of 
communication" a but more obvious it includes a definition of the MyApp 
class.

#!/usr/bin/env python
import wx
import femGUI

class S: pass

s = S()

class MyApp(wx.App):
     "You can throw away the MyApp in femGUI."

     def OnInit(self):
         myFrame = femGUI.FemInput(s) # passes storage object in
         self.SetTopWindow(myFrame)
         myFrame.Show()
         return True

app = MyApp(False)
app.MainLoop()

print dir(storage)

The statement at the end is just to show you that the attributes exist. 
 From there you can do what you want.

Now it turns out you don't need to make that many modifications to your 
class definition.
> 
> 
> THE FEMINPUT GUI CLASS
> 
> import wx
> 
> class FemInput(wx.Frame):
>     def __init__(self):

The above line has to change because we have to accept, and store, a 
reference to the storage object we provide in the FemInput constructor.

     def __init__(self, storage):
         self.storage = storage

Most if it is pretty much standard from here on, but keep reading.

>         wx.Frame.__init__(self, None, -1, "Options Input Interface")
>         panel = wx.Panel(self)
> 
>         # First create the controls
> 
>         # Title
>         topLbl = wx.StaticText(panel, -1, "FEM 2D Basket Put Option
> ",size=(420,-1))
>         topLbl.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
> 
>         # S1 lower and upper bounds for grid
>         s1label = wx.StaticText(panel, -1, "S1 Low , S2 Low: ",
> size=(220,-1))
>         self.s1lower = wx.TextCtrl(panel, -1, "", size=(100,-1));
>         self.s2lower = wx.TextCtrl(panel, -1, "", size=(100,-1));
> 
>         # S2 lower and upper bounds for grid
>         s2label = wx.StaticText(panel, -1, "S1 High, S2 High: ",
> size=(220,-1))
>         self.s1upper = wx.TextCtrl(panel, -1, "", size=(100,-1));
>         self.s2upper = wx.TextCtrl(panel, -1, "", size=(100,-1));
> 
>         # S1 and S2 volatility
>         vlabel = wx.StaticText(panel, -1, "S1 Volatility, S2
> Volatility: ", size=(220,-1))
>         self.v1vol  = wx.TextCtrl(panel, -1, "", size=(100,-1));
>         self.v2vol  = wx.TextCtrl(panel, -1, "", size=(100,-1));
> 
>         # Risk free rate and correlation
>         prlabel = wx.StaticText(panel, -1, "Interest Rate,
> Correlation: ", size=(220,-1))
>         self.risk    = wx.TextCtrl(panel, -1, "", size=(100,-1));
>         self.corr    = wx.TextCtrl(panel, -1, "", size=(100,-1));
> 
> 
>         # Strike and Exercise Date
>         kTlabel = wx.StaticText(panel, -1, "Srike Price, Exercise
> Date: ", size=(220,-1))
>         self.strike    = wx.TextCtrl(panel, -1, "", size=(100,-1));
>         self.finalT    = wx.TextCtrl(panel, -1, "", size=(100,-1));
> 
>         # deltaT and deltaX
>         dTXlabel = wx.StaticText(panel, -1, "delta T, delta X: ",
> size=(220,-1))
>         self.deltaT    = wx.TextCtrl(panel, -1, "", size=(100,-1));
>         self.deltaX    = wx.TextCtrl(panel, -1, "", size=(100,-1));
> 
> 
>         # Execute program
>         runBtn = wx.Button(panel, -1, "Run")
>         self.Bind(wx.EVT_BUTTON, self.OnSubmit, runBtn)
> 
>         # Now do the layout.
> 
>         # mainSizer is the top-level one that manages everything
>         mainSizer = wx.BoxSizer(wx.VERTICAL)
>         mainSizer.Add(topLbl, 0, wx.ALL, 5)
>         mainSizer.Add(wx.StaticLine(panel), 0,
>                 wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
> 
>         # femSizer is a grid that holds all of the address info
>         femSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
>         femSizer.AddGrowableCol(1)
> 
>         # S1 and S2 LOWER label
>         femSizer.Add(s1label, 0,
>                 wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
>         # the lower and upper S1 bounds are in a sub-sizer
>         s1Sizer = wx.BoxSizer(wx.HORIZONTAL)
>         s1Sizer.Add(self.s1lower, 1)
>         s1Sizer.Add((10,10)) # some empty space
>         s1Sizer.Add(self.s2lower, 1, wx.LEFT|wx.RIGHT, 5)
>         femSizer.Add(s1Sizer, 1, wx.EXPAND)
> 
> 
>         # S1 and S2 HIGH label
>         femSizer.Add(s2label, 0,
>                 wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
>         # the lower and upper S1 bounds are in a sub-sizer
>         s2Sizer = wx.BoxSizer(wx.HORIZONTAL)
>         s2Sizer.Add(self.s1upper, 1)
>         s2Sizer.Add((10,10)) # some empty space
>         s2Sizer.Add(self.s2upper, 1, wx.LEFT|wx.RIGHT, 5)
>         femSizer.Add(s2Sizer, 1, wx.EXPAND)
> 
> 
>         # Volatility label
>         femSizer.Add(vlabel, 0,
>                 wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
>         # the lower and upper S1 bounds are in a sub-sizer
>         volSizer = wx.BoxSizer(wx.HORIZONTAL)
>         volSizer.Add(self.v1vol, 1)
>         volSizer.Add((10,10)) # some empty space
>         volSizer.Add(self.v2vol, 1, wx.LEFT|wx.RIGHT, 5)
>         femSizer.Add(volSizer, 1, wx.EXPAND)
> 
> 
>         # Risk free Rate and corelation
>         femSizer.Add(prlabel, 0,
>                 wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
>         # the lower and upper S1 bounds are in a sub-sizer
>         rcSizer = wx.BoxSizer(wx.HORIZONTAL)
>         rcSizer.Add(self.risk, 1)
>         rcSizer.Add((10,10)) # some empty space
>         rcSizer.Add(self.corr, 1, wx.LEFT|wx.RIGHT, 5)
>         femSizer.Add(rcSizer, 1, wx.EXPAND)
> 
> 
>         # Strike and Exercise Date
>         femSizer.Add(kTlabel, 0,
>                 wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
>         # the lower and upper S1 bounds are in a sub-sizer
>         ktSizer = wx.BoxSizer(wx.HORIZONTAL)
>         ktSizer.Add(self.strike, 1)
>         ktSizer.Add((10,10)) # some empty space
>         ktSizer.Add(self.finalT, 1, wx.LEFT|wx.RIGHT, 5)
>         femSizer.Add(ktSizer, 1, wx.EXPAND)
> 
> 
>         # deltaT and deltaX
>         femSizer.Add(dTXlabel, 0,
>                 wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
>         # the lower and upper S1 bounds are in a sub-sizer
>         dtxSizer = wx.BoxSizer(wx.HORIZONTAL)
>         dtxSizer.Add(self.deltaT, 1)
>         dtxSizer.Add((10,10)) # some empty space
>         dtxSizer.Add(self.deltaX, 1, wx.LEFT|wx.RIGHT, 5)
>         femSizer.Add(dtxSizer, 1, wx.EXPAND)
> 
> 
>         # now add the femSizer to the mainSizer
>         mainSizer.Add(femSizer, 0, wx.EXPAND|wx.ALL, 10)
> 
>         # The buttons sizer will put them in a row with resizeable
>         # gaps between and on either side of the buttons
>         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
>         btnSizer.Add((10,10)) # some empty space
>         btnSizer.Add(runBtn)
>         btnSizer.Add((10,10)) # some empty space
>         mainSizer.Add(btnSizer, 0, wx.EXPAND|wx.BOTTOM, 10)
> 
>         panel.SetSizer(mainSizer)
> 
>         # Fit the frame to the needs of the sizer.  The frame will
>         # automatically resize the panel as needed.  Also prevent the
>         # frame from getting smaller than this size.
>         mainSizer.Fit(self)
>         mainSizer.SetSizeHints(self)
> 
>     def OnSubmit(self, evt):
>         s1low  = self.s1lower.GetValue()
>         s2low  = self.s2lower.GetValue()
>         s1high = self.s1upper.GetValue()
>         s2high = self.s2upper.GetValue()
>         s1vol  = self.v1vol.GetValue()
>         s2vol  = self.v2vol.GetValue()
>         irate  = self.risk.GetValue()
>         pcorr  = self.corr.GetValue()
>         kprice = self.strike.GetValue()
>         totalT = self.finalT.GetValue()
>         delT   = self.deltaT.GetValue()
>         delX   = self.deltaX.GetValue()
>         wx.MessageBox('You chose: \n %s \n %s \n %s \n %s \
>             \n %s \n %s \n %s' %
> (s1low,s2low,s1high,s2high,s1vol,s2vol,irate))
> #        I want to do something like this below....
> #        return s1low,s2low,s1high,s2high,s1vol,s2vol,irate
> 
This is the crux. Here you have to Destroy() the frame (self) for the 
MainLoop to terminate properly, so you have to save the values before 
you do that. So replace this whole method with something like

     def OnSubmit(self, evt):
         "The user has filled in the required values and want to run"
         self.storage.s1low  = self.s1lower.GetValue()
         self.storage.s2low  = self.s2lower.GetValue()
         self.storage.s1high = self.s1upper.GetValue()
         self.storage.s2high = self.s2upper.GetValue()
         self.storage.s1vol  = self.v1vol.GetValue()
         self.storage.s2vol  = self.v2vol.GetValue()
         self.storage.irate  = self.risk.GetValue()
         self.storage.pcorr  = self.corr.GetValue()
         self.storage.kprice = self.strike.GetValue()
         self.storage.totalT = self.finalT.GetValue()
         self.storage.delT   = self.deltaT.GetValue()
         self.storage.delX   = self.deltaX.GetValue()
         self.Destroy()

> 
> 
> class MyApp(wx.App):
> 
>     def OnInit(self):
>         frame = FemInput()
>         self.SetTopWindow(frame)
>         frame.Show()
>         return True
> 
> 
> # Needed if called as a module
> if __name__ == '__main__':
>     app = MyApp(False)
>     app.MainLoop()
> 


Good luck!

regards
  Steve
-- 
Steve Holden       +44 150 684 7255  +1 800 494 3119
Holden Web LLC/Ltd          http://www.holdenweb.com
Skype: holdenweb     http://del.icio.us/steve.holden
Recent Ramblings       http://holdenweb.blogspot.com




More information about the Python-list mailing list