Quantcast

Jump to content


Photo

[How To] Noobies Guide to wxPython


  • Please log in to reply
4 replies to this topic

#1 Pyro699

Pyro699
  • 1,398 posts


Users Awards

Posted 09 November 2010 - 09:30 AM

Hey,

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 :D

Until Next Time
~Cody Woolaver

#2 Noitidart

Noitidart
  • Neocodex Co-Founder

  • 23,214 posts


Users Awards

Posted 09 November 2010 - 10:23 AM

Wow...
Absolutely absolutely excellent. I apprecaite this because I'm trying to learn Python myself.
Raui could appreciate this as well. Amazing thanks pyro!

#3 Pyro699

Pyro699
  • 1,398 posts


Users Awards

Posted 14 November 2010 - 09:40 PM

Not a problem Noit :) I know the struggles one can have when getting into GUI programming and it can defiantly be a large hassle... I am a little disappointed no one is asking any questions ;) Without questions i wont be able to provide more assistance :3

I have figured out a good method for longer process programming in gui's and will write up another guide sometime soon :)

~Cody

#4 Crispin

Crispin
  • 48 posts

Posted 06 January 2012 - 01:18 AM

*Bookmarks*

#5 ChristianCareaga

ChristianCareaga
  • 40 posts

Posted 05 August 2013 - 12:21 PM

Instead of making multiple similar lines like that could you use a for loop to create the buttons ?


0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users