Designing a Calculator in wxPython

wxPython is a library that makes it easy for Python programmers to build graphical user interfaces. Over the past few weeks, you have seen some articles covering this library. This week, you will learn how to create a simple but useful application with wxPython.

Introduction

This article assumes that you have only a little bit of skills with wxPython. You might want to read A Look at wxPython, Organization in wxPython and A Close Look at a Few wxPython Controls before reading this article, as they cover some material you will need to know to get the most out of it. This article covers the basics of designing a useful application in wxPython, thus  gluing knowledge together to build something functional.

I find myself using Microsoft’s Calculator program frequently. It’s a very useful utility, even if it is extremely simple. Let’s try to make a calculator similar to Microsoft’s Calculator in Python. It’s challenging to create, though it’s not so tough that it requires us to go out of our way to learn anything major. We’ll just try to create a simple copy. The scientific features of Microsoft’s Calculator require other Python libraries, and the purpose of this article is to teach wxPython, not math.

{mospagebreak title=The Plan}

The best way to organize our calculator is to use a wxGridBagSizer. Our buttons will almost be the same size and will be organized in rows and columns, so a wxGridBagSizer is perfect for the job. The wxGridBagSizer is also pretty simple to use, so that’s another plus.

Our calculator isn’t going to be very complex, as I mentioned before. On the first row of our calculator will be the display, which will be served by a read-only wxTextCtrl. The display will take up the entire row. The second row will contain the add to memory button, a button for the number one, a button for the number two, a button for the number three and an addition button. The third row will contain a delete from memory button, a button for the number four, a button for the number five, a button for the number six and a subtraction button. The fourth row will contain a memory recall button, a button for the number seven, a button for the number eight, a button for the number nine and a multiplication button. The fifth row will contain a wxStaticText that displays the number currently in memory, a decimal button, a zero button, a solve button and a division button. The final row will contain a two-column clear button, a backspace button, a positive/negative button and a square root button.

We’ll save ourselves the pain of creating over a dozen methods by making use of event ID numbers. I’ll show you how to do this once we’re at that point.

Let’s get to it.

{mospagebreak title=Creating the Layout}

Let’s start with a frame and make our way forward. Go ahead and put down the basics:

from wxPython.wx import *

class CalculatorFrame ( wxFrame ):

   def __init__ ( self ):

      wxFrame.__init__ ( self, None, -1, ‘PyCalc’ )

      self.Show ( True )

calculator = wxPySimpleApp()

CalculatorFrame()

calculator.MainLoop()

Now we’ll add the wxGridBagSizer and the display. Borders of 1 and 1 look good with our calculator, though you can pick whatever reasonable (or unreasonable) value you want:

from wxPython.wx import *

class CalculatorFrame ( wxFrame ):

   def __init__ ( self ):

      wxFrame.__init__ ( self, None, -1, ‘PyCalc’ )

      # Create a panel

      self.panel = wxPanel ( self, -1 )

      # Create a wxGridBagSizer

      self.sizer = wxGridBagSizer ( 1, 1 )

      # Add a display that takes up the whole row

      self.display = wxTextCtrl ( self.panel, -1, ‘0.’, style = wxTE_READONLY | wxTE_RIGHT )

      self.sizer.Add ( self.display, ( 0, 0 ), ( 1, 5 ), wxEXPAND )

      # Attach the sizer and adjust the frame size

      self.panel.SetSizerAndFit ( self.sizer )

      self.SetClientSize ( self.panel.GetSize() )

      self.Show ( True )

calculator = wxPySimpleApp()

CalculatorFrame()

calculator.MainLoop()

If you execute our calculator, it looks horrid, but I promise you that things will improve once we’re finished. Next come the buttons. Instead of calling wxGridBagSizer‘s Add method over a dozen times, we’ll create a list for our buttons. The list will contain one list for each row. These lists will contain one list for each button, which will contain the button’s text and the button’s ID number. For spaces we do not want to occupy with buttons, we will put in None. This may sound complicated in a paragraph, but it makes a lot more sense when we put it in Python. We will then add everything to the grid with a loop:

from wxPython.wx import *

class CalculatorFrame ( wxFrame ):

   def __init__ ( self ):

      wxFrame.__init__ ( self, None, -1, ‘PyCalc’ )

      self.panel = wxPanel ( self, -1 )

      self.sizer = wxGridBagSizer ( 1, 1 )

      self.display = wxTextCtrl ( self.panel, -1, ‘0.’, style = wxTE_READONLY | wxTE_RIGHT )

      self.sizer.Add ( self.display, ( 0, 0 ), ( 1, 5 ), wxEXPAND )

      # Create the list of lists of lists of buttons

      buttons = [ [ None, None, None, None, None ],

                  [ [ 'M+', 100 ], [ '1', 1 ], [ '2', 2 ], [ '3', 3 ], [ '+', 200 ] ],

                  [ [ 'M-', 101 ], [ '4', 4 ], [ '5', 5 ], [ '6', 6 ], [ '-', 201 ] ],

                  [ [ 'MR', 102 ], [ '7', 7 ], [ '8', 8 ], [ '9', 9 ], [ '*', 202 ] ],

                  [ None, [ '.', 103 ], [ '0', 0 ], [ '=', 104 ], [ '/', 203 ] ],

                  [ None, None, [ 'B', 105 ], [ '+/-', 106 ], [ 'sqrt', 204 ] ] ]

      # Add the buttons

      x = y = 0

      for row in buttons:

         for button in row:

            if button == None:

               x = x + 1

               continue

            self.sizer.Add ( wxButton ( self.panel, button [ 1 ], button [ 0 ], size = ( 30, 30 ) ), ( y, x ) )

            x = x + 1

         x = 0

         y = y + 1

      self.panel.SetSizerAndFit ( self.sizer )

      self.SetClientSize ( self.panel.GetSize() )

      self.Show ( True )

calculator = wxPySimpleApp()

CalculatorFrame()

calculator.MainLoop()

Finally, we’ll add our memory display and our two-column clear button:

from wxPython.wx import *

class CalculatorFrame ( wxFrame ):

   def __init__ ( self ):

      wxFrame.__init__ ( self, None, -1, ‘PyCalc’ )

      self.panel = wxPanel ( self, -1 )

      self.sizer = wxGridBagSizer ( 1, 1 )

      self.display = wxTextCtrl ( self.panel, -1, ‘0.’, style = wxTE_READONLY | wxTE_RIGHT )

      self.sizer.Add ( self.display, ( 0, 0 ), ( 1, 5 ), wxEXPAND )

      # Add the memory display and center everything

      self.memoryDisplay = wxStaticText ( self.panel, -1, ‘0’, style = wxALIGN_CENTER )

      self.sizer.Add ( self.memoryDisplay, ( 4, 0 ), ( 1, 1 ), wxALIGN_CENTER )

      # Add the two-column clear button

      self.sizer.Add ( wxButton ( self.panel, 107, ‘Clear’, size = ( 30, 30 ) ), ( 5, 0 ), ( 1, 2 ), wxEXPAND )

      buttons = [ [ None, None, None, None, None ],

                  [ [ 'M+', 100 ], [ '1', 1 ], [ '2', 2 ], [ '3', 3 ], [ '+', 200 ] ],

                  [ [ 'M-', 101 ], [ '4', 4 ], [ '5', 5 ], [ '6', 6 ], [ '-', 201 ] ],

                  [ [ 'MR', 102 ], [ '7', 7 ], [ '8', 8 ], [ '9', 9 ], [ '*', 202 ] ],

                  [ None, [ '.', 103 ], [ '0', 0 ], [ '=', 104 ], [ '/', 203 ] ],

                  [ None, None, [ 'B', 105 ], [ '+/-', 106 ], [ 'sqrt', 204 ] ] ]

      x = y = 0

      for row in buttons:

         for button in row:

            if button == None:

               x = x + 1

               continue

            self.sizer.Add ( wxButton ( self.panel, button [ 1 ], button [ 0 ], size = ( 30, 30 ) ), ( y, x ) )

            x = x + 1

         x = 0

         y = y + 1

      self.panel.SetSizerAndFit ( self.sizer )

      self.SetClientSize ( self.panel.GetSize() )

      self.Show ( True )

calculator = wxPySimpleApp()

CalculatorFrame()

calculator.MainLoop()

Our layout is now finished. If you wish, you can adjust the sizes and borders to fit your tastes.

{mospagebreak title=Wiring It All Together}

Now for the events. We need to catch the events thrown by our buttons. This will require a modification of our button loop, as well as some work for our clear button. It would also be wise to set a maximum length for our dislay:

from wxPython.wx import *

class CalculatorFrame ( wxFrame ):

   def __init__ ( self ):

      wxFrame.__init__ ( self, None, -1, ‘PyCalc’ )

      self.panel = wxPanel ( self, -1 )

      self.sizer = wxGridBagSizer ( 1, 1 )

      self.display = wxTextCtrl ( self.panel, -1, ‘0.’, style = wxTE_READONLY | wxTE_RIGHT )

      # Add a maximum length

      self.display.SetMaxLength ( 5 )

      self.sizer.Add ( self.display, ( 0, 0 ), ( 1, 5 ), wxEXPAND )

      self.memoryDisplay = wxStaticText ( self.panel, -1, ‘0’, style = wxALIGN_CENTER )

      self.sizer.Add ( self.memoryDisplay, ( 4, 0 ), ( 1, 1 ), wxALIGN_CENTER )

      self.sizer.Add ( wxButton ( self.panel, 107, ‘Clear’, size = ( 30, 30 ) ), ( 5, 0 ), ( 1, 2 ), wxEXPAND )

      # Catch the event thrown by our clear button

      EVT_BUTTON ( self.panel, 107, self.handler )

      buttons = [ [ None, None, None, None, None ],

                  [ [ 'M+', 100 ], [ '1', 1 ], [ '2', 2 ], [ '3', 3 ], [ '+', 200 ] ],

                  [ [ 'M-', 101 ], [ '4', 4 ], [ '5', 5 ], [ '6', 6 ], [ '-', 201 ] ],

                  [ [ 'MR', 102 ], [ '7', 7 ], [ '8', 8 ], [ '9', 9 ], [ '*', 202 ] ],

                  [ None, [ '.', 103 ], [ '0', 0 ], [ '=', 104 ], [ '/', 203 ] ],

                  [ None, None, [ 'B', 105 ], [ '+/-', 106 ], [ 'sqrt', 204 ] ] ]

      x = y = 0

      for row in buttons:

         for button in row:

            if button == None:

               x = x + 1

               continue

            self.sizer.Add ( wxButton ( self.panel, button [ 1 ], button [ 0 ], size = ( 30, 30 ) ), ( y, x ) )

            # Catch the events thrown by our buttons

            EVT_BUTTON ( self.panel, button [ 1 ], self.handler )

            x = x + 1

         x = 0

         y = y + 1

      self.panel.SetSizerAndFit ( self.sizer )

      self.SetClientSize ( self.panel.GetSize() )

      self.Show ( True )

calculator = wxPySimpleApp()

CalculatorFrame()

calculator.MainLoop()

Brace yourself. We’re about to take a giant leap here. The first change we’ll make is that we’ll import the math module, needed for square roots. The second change will be to create some variables necessary to perform operations. Finally, we’ll add the code required to perform operations:

from wxPython.wx import *

import math

class CalculatorFrame ( wxFrame ):

   def __init__ ( self ):

      wxFrame.__init__ ( self, None, -1, ‘PyCalc’ )

      self.panel = wxPanel ( self, -1 )

      self.sizer = wxGridBagSizer ( 1, 1 )

      self.display = wxTextCtrl ( self.panel, -1, ‘0.’, style = wxTE_READONLY | wxTE_RIGHT )

      self.display.SetMaxLength ( 5 )

      self.sizer.Add ( self.display, ( 0, 0 ), ( 1, 5 ), wxEXPAND )

      self.memoryDisplay = wxStaticText ( self.panel, -1, ‘0’, style = wxALIGN_CENTER )

      self.sizer.Add ( self.memoryDisplay, ( 4, 0 ), ( 1, 1 ), wxALIGN_CENTER )

      self.sizer.Add ( wxButton ( self.panel, 107, ‘Clear’, size = ( 30, 30 ) ), ( 5, 0 ), ( 1, 2 ), wxEXPAND )

      EVT_BUTTON ( self.panel, 107, self.handler )

      buttons = [ [ None, None, None, None, None ],

                  [ [ 'M+', 100 ], [ '1', 1 ], [ '2', 2 ], [ '3', 3 ], [ '+', 200 ] ],

                  [ [ 'M-', 101 ], [ '4', 4 ], [ '5', 5 ], [ '6', 6 ], [ '-', 201 ] ],

                  [ [ 'MR', 102 ], [ '7', 7 ], [ '8', 8 ], [ '9', 9 ], [ '*', 202 ] ],

                  [ None, [ '.', 103 ], [ '0', 0 ], [ '=', 104 ], [ '/', 203 ] ],

                  [ None, None, [ 'B', 105 ], [ '+/-', 106 ], [ 'sqrt', 204 ] ] ]

      x = y = 0

      for row in buttons:

         for button in row:

            if button == None:

               x = x + 1

               continue

            self.sizer.Add ( wxButton ( self.panel, button [ 1 ], button [ 0 ], size = ( 30, 30 ) ), ( y, x ) )

            EVT_BUTTON ( self.panel, button [ 1 ], self.handler )

            x = x + 1

         x = 0

         y = y + 1

      # Add a variables for memory, last display and operation

      self.memory = 0

      self.last = None

      self.operation = None

      self.panel.SetSizerAndFit ( self.sizer )

      self.SetClientSize ( self.panel.GetSize() )

      self.Show ( True )

   def handler ( self, event ):

      # Retrieve the event’s ID number

      id = event.GetId()

      # If the event ID corresponds to a number button, append the number

      # If there is a leading zero, we’ll get rid of it

      if ( id >= 0 ) & ( id <= 9 ):

         if self.display.GetValue() == ‘0.’:

            self.display.SetValue ( ” )

         self.display.AppendText ( str ( id ) )

      # Add to memory

      elif id == 100:

         self.memory = float ( self.display.GetValue() )

      # Clear memory

      elif id == 101:

         self.memory = 0

      # Recall memory if there’s anything to recall

      elif id == 102:

         if self.memory != 0:

            self.display.SetValue ( str ( self.memory ) )

      # Add a decimal, if there is not already one

      elif id == 103:

         if self.display.GetValue().find ( ‘.’ ) == -1:

            self.display.AppendText ( ‘.’ )

      # Solve

      elif id == 104:

         self.solve()

      # Backspace, if we can

      elif id == 105:

         if len ( self.display.GetValue() ) > 1:

            self.display.SetValue ( self.display.GetValue() [ :-1 ] )

         elif len ( self.display.GetValue() ) == 1:

            self.display.SetValue ( ‘0.’ )

      # Make the number negative or positive by multiplying it by -1

      elif id == 106:

         self.display.SetValue ( str ( float ( self.display.GetValue() ) * -1 ) )

      # Clear

      elif id == 107:

         self.display.SetValue ( ‘0.’ )

         self.last = None

         self.operation = None

      # Addition: put the current display in self.last and put “+” in self.operation

      # Solve if necessary

      elif id == 200:

         self.solve()

         self.last = self.display.GetValue()

         self.operation = ‘+’

         self.display.SetValue ( ‘0.’ )

      # Subtraction, similar to addition

      elif id == 201:

         self.solve()

         self.last = self.display.GetValue()

         self.operation = ‘-‘

         self.display.SetValue ( ‘0.’ )

      # Multiplication

      elif id == 202:

         self.solve()

         self.last = self.display.GetValue()

         self.operation = ‘*’

         self.display.SetValue ( ‘0.’ )

      # Division

      elif id == 203:

         self.solve()

         self.last = self.display.GetValue()

         self.operation = ‘/’

         self.display.SetValue ( ‘0.’ )

      # Square root

      elif id == 204:

         if float ( self.display.GetValue() ) > 0:

            self.display.SetValue ( str ( math.sqrt ( float ( self.display.GetValue() ) ) ) )

   def solve ( self ):

      if ( self.last != None ) & ( self.operation != None ):

         self.display.SetValue ( str ( eval ( str ( self.last ) + self.operation + str ( self.display.GetValue() ) ) ) )

         self.last = None

         self.operation = None

calculator = wxPySimpleApp()

CalculatorFrame()

calculator.MainLoop()

It seems like a lot, but it’s not that complicated upon closer examination. The handler method checks to see what the event ID is equal to. Notice how the ID numbers correspond to the appropriate buttons. It then performs the required operation, manipulating the display and three variables: memory, last and operation. The memory variable contains the number currently in memory. The last variable contains the previous number in the display. The operation variable contains the current operation.

When we’re required to solve something, we just evaluate the last variable, the operation variable and the number in the display. For example, let’s say last is 25, operation is “/” and 5 is in the display. This would be evaluated:

25/5

Evaluating that will, of course, produce 5.

Conclusion

We’ve now created a useful application with limited knowledge of wxPython. The simplicity of it all proves how easy it is to create graphical user interfaces in Python applications by using the wxPython library. In fact, the most complicated thing we did in the whole application was create a system that would allow us to easily perform operations and make our calculator work.

There’s still a lot more to wxPython, but we can proceed forward a bit faster now that we’ve jumped over a major hurdle.

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan