So im sure many of you know that i already wrote a quicky introduction guide to python. If you havn't read that, i suggest you do as i wont be dumbing down some of the tasks that need to be done here.
Now, i will admit i am not the best GUI programmer out there, im probably pretty far down on that list. One thing that i do know how to do though is organize my code so that it is not all clustered. A lot of layouts can have hundreds or even thousands of different parts that all have to be working together in sync. The guides that are present on wxPython's main site are AWESOME tutorials, but they lack the 'real world' touch if you know what i mean.
In this guide i will give you my method for creating GUI's and being able to manage the code in a way that will let you easily glide through your giant script. Again, this isnt really a guide for how to create the code needed, it is a guide to help you manage your code better
In this example I'm gonna create a simple calculator. If you read wxPythons tutorial they give you an example of how to make a calculator, but the code is thrown together rather quickly and if you needed to debug anywhere it would be rather difficult to locate problematic areas.
Lets start of by breaking down each of the components... We will have:
- 19 Buttons
- 1 Text Area
Now, that is a lot of buttons to have on a single panel... lets try breaking that up some more
- 19 Buttons
- 10 Numbers
- 4 Operators
- 3 Commands
- 2 Other
- 1 Text Area
Now that we have the basic layout down, lets create those objects. What i like to do is create 2 dimensional dictionaries and name each of my items very well Here is how i would create this layout:
_buttons = {
'Numbers': {
'0': wx.Button(self, wx.NewId(), "0"),
'1': wx.Button(self, wx.NewId(), "1"),
'2': wx.Button(self, wx.NewId(), "2"),
'3': wx.Button(self, wx.NewId(), "3"),
'4': wx.Button(self, wx.NewId(), "4"),
'5': wx.Button(self, wx.NewId(), "5"),
'6': wx.Button(self, wx.NewId(), "6"),
'7': wx.Button(self, wx.NewId(), "7"),
'8': wx.Button(self, wx.NewId(), "8"),
'9': wx.Button(self, wx.NewId(), "9")
},
'Operators': {
'Add': wx.Button(self, wx.NewId(), "+"),
'Sub': wx.Button(self, wx.NewId(), "-"),
'Mul': wx.Button(self, wx.NewId(), "*"),
'Div': wx.Button(self, wx.NewId(), "/")
},
'Commands': {
'Back': wx.Button(self, wx.NewId(), "Back"),
'Clear': wx.Button(self, wx.NewId(), "Clear"),
'Close': wx.Button(self, wx.NewId(), "Close")
},
'Others': {
'Dec': wx.Button(self, wx.NewId(), "."),
'Eq': wx.Button(self, wx.NewId(), "=")
}
}
_text = {
'Main': {
'Response': wx.TextCtrl(self, wx.NewId(), "")
}
}
Now, doing this you are easily able to see what it is that is going to do what task even before we have placed anything on the frame. You can also note that i placed the text box in a 2 dimensional dictionary even though it didn't need to be. I did this to keep consistency throughout my code.
Now, lets add those controls to a frame...
Sizer_0 = wx.BoxSizer(wx.VERTICAL)
Sizer_1 = wx.wx.GridBagSizer(5,4)
#Numbers
Sizer_1.Add(self._buttons['Numbers']['0'], (4,0), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['1'], (3,0), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['2'], (3,1), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['3'], (3,2), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['4'], (2,0), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['5'], (2,1), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['6'], (2,2), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['7'], (1,0), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['8'], (1,1), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Numbers']['9'], (1,2), (1,1), wx.EXPAND)
#Operators
Sizer_1.Add(self._buttons['Operators']['Add'], (1,3), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Operators']['Sub'], (2,3), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Operators']['Mul'], (3,3), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Operators']['Div'], (4,3), (1,1), wx.EXPAND)
#Commands
Sizer_1.Add(self._buttons['Commands']['Back'], (0,1), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Commands']['Clear'], (0,0), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Commands']['Close'], (0,3), (1,1), wx.EXPAND)
#Others
Sizer_1.Add(self._buttons['Others']['Eq'], (4,2), (1,1), wx.EXPAND)
Sizer_1.Add(self._buttons['Others']['Dec'], (4,1), (1,1), wx.EXPAND)
Sizer_0.Add(self._text['Main']['Response'], 0, wx.EXPAND, 0)
Sizer_0.Add(Sizer_1, 1, wx.EXPAND, 0)
self.SetSizer(Sizer_0)
Sizer_0.Fit(self)
self.Layout()
Now, i've found that naming each of your sizers something special gets very confusing after you have 5 layers of sub sizers... I always start at Sizer_0 and go to Sizer_n this way i wont be distracted by the name... We are only using 2 sizers in this example but in code later on you will see that doing this really does help. Here is a screen shot from my own AutoPricer where i did just that...
Spoiler
Now... we have all the controls we need, and they are placed properly on our frame. Lets start to bind each of the controls to a specific function.
for ctrlId in self._buttons['Numbers']:
self.Bind(wx.EVT_BUTTON, self.PutNum, self._buttons['Numbers'][ctrlId])
self.Bind(wx.EVT_BUTTON, self.PutNum, self._buttons['Others']['Dec'])
for ctrlId in self._buttons['Operators']:
self.Bind(wx.EVT_BUTTON, self.PutOpp, self._buttons['Operators'][ctrlId])
self.Bind(wx.EVT_BUTTON, self.Calc, self._buttons['Others']['Eq'])
self.Bind(wx.EVT_BUTTON, self.Back, self._buttons['Commands']['Back'])
self.Bind(wx.EVT_BUTTON, self.Clear, self._buttons['Commands']['Clear'])
self.Bind(wx.EVT_BUTTON, self.DoClose, self._buttons['Commands']['Close'])
You can see where having the dictionaries comes in handy for repetitive tasks We now have each button bound to a different function... lets create those functions now
Here are the functions i came up with
def PutNum(self, event):
eObj = event.GetEventObject()
self._text['Main']['Response'].AppendText(eObj.GetLabelText())
def PutOpp(self, event):
eObj = event.GetEventObject()
self._text['Main']['Response'].AppendText(eObj.GetLabelText())
def Calc(self, event):
cForm = self._text['Main']['Response'].GetValue()
try:
self.Clear(None)
result = eval(cForm)
self._text['Main']['Response'].AppendText(str(result))
except StandardError:
self._text['Main']['Response'].AppendText("Syntax Error")
def Back(self, event):
cVal = self._text['Main']['Response'].GetValue()
self.Clear(None)
self._text['Main']['Response'].SetValue(cVal[:-1])
def Clear(self, event):
self._text['Main']['Response'].SetValue("")
def DoClose(self, event):
self.Close()
The PutOpp and PutNum functions are exactly the same, so there wasn't a real need to separate them. What i did was instead of creating a separate function for each individual button, I set all of the number and operator buttons to the same function. It would take their label and append that to the result window. The rest should be pretty self explanatory.
There isn't much left for me to do except compile the entire script and present it to you. Again i hope this makes programming in GUI's easier for you, it did for me This is just the basics though, and all i feel comfortable writing a guide for. As i learn better methods for more complex scripting, i will create another How-To
Full Script:
Spoiler
Feel free to ask any questions
Until Next Time
~Cody Woolaver