Tag: Computer science

Writing a calculator program in Python

Python based calculator using tkinter

Many computer courses in Python suggest writing a calculator program in Python to develop both your logic and user interface skills. In this article we discuss some more advanced concepts you might use to write a good calculator program with a minimum of effort. We are not, however, proposing to do your entire assignment, but only to give you some helpful pointers that may make your program easier to write well.

If you look at our calculator interface, you quickly note that it is really a 5 x 4 matrix of buttons, but actually 6 x 4 counting the value display line, which actually just a Label field. So even if you haven’t bothered to learn about layout managers (there are only two in tkinter) now would be a good time to start.

It is also helpful to note that all of the numerical buttons (all the gray ones) have the same function: they put a number into the display line. The only exception is the period button which also has to check to make sure that only one period gets added to the number.

Command buttons

The trouble with an interface using all these buttons is deciding where the clicks for all these buttons go. One really simple solution is to put the command to be executed right inside the button.  Here is the interface for such buttons:

# abstract class defines command interface
class DButton (Button):
    def __init__(self, master, **kwargs):
        super().__init__(master, command=self.comd, **kwargs)
   
    def comd(self):
        pass

This is an abstract class, in that the comd function just has a place holder pass statement. But if we derive buttons from DButton, we can fill in each comd method with actual code as we see below. All of the buttons in this example are derived from DButton and each derived button has its own comd code.

In all cases, these buttons will not know much about the value display or the state of the period or anything else. Instead, we create a Mediator class that knows about the value display and receives commands from all the buttons. So, our Numeric button is just the following: mostly just setting up the font and color:

class NumButton(DButton):
    def __init__(self, master, med, **kwargs):   
        super().__init__(master, width=2, fg='white', 
                       bg="#aaa", **kwargs)

        butnfont = tkFont.Font(family='Helvetica',size=16, weight='bold')
        self['font'] = butnfont
        self.med= med         # save copy of Mediator

   

 Our comd function calls the Mediator’s numClick method and sends it the label of the button, which is the text representing the actual number we want to add to the value display.

def comd(self):
        self.med.numClick(self['text']) # gets number from text label 

At the top of the list of widgets we create the top label, and then we create the Mediator and pass it that label. The Mediator is the only class that knows about the contents of that label, and it receives calls from the various number buttons to add that text to the label. In this method it adds a leading space, which could be changed to a minus sign if you change the numbers sign, and then just appends numbers to the label:

# Any number is clicked
def numClick(self, text):
    if self.first: # if first char clear the zero
        self.setlabelText(" ") # leading space
        self.first = False
    st = self.getLabelText() + text
    self.setlabelText(st)

So, we create all of the number buttons in a grid as shown in the figure. Here is a bit of that code for buttons 7, 8 and 9. They are placed in grid row 2 in columns 0, 1 and 2.

but = NumButton(root, med, text='7')
but.grid(row=2, column=0, padx=1, pady=1)
but = NumButton(root, med, text='8')
but.grid(row=2, column=1, padx=1, pady=1)
but = NumButton(root, med, text='9')
but.grid(row=2, column=2, padx=1, pady=1)

To illustrate the structure of this part of the program, the Mediator communicates with the label and the buttons all communicate with the Mediator:

In a similar fashion, all the other buttons are command buttons that tell the Mediator what to do. The C and CE button clear all the data and the CE buttons just clears the text in that top label.

Clicking one of the operation buttons (+, -, *, or / ) tells the program that the current number is done and that a new one will start so that the two numbers needed to carry out that operations get created. You should store each of those in some sort of array or stack along with the operation symbol. Usually this array can be stored right in the Mediator class. Then, when the Equals sign is clicked, you carry out the calculation.

If you enter

2 + 3

the Equals sign button should combine those and display “5.0”. (We convert all of them to floats for simplicity.)

This will work for more than two numbers, and combining them left to right mainly works:

5 * 3 + 2

will correctly display 17.  However, if you remember the priority rule My Dear Aunt Sally, you carry out multiplication and division before addition and subtraction, and simple to right combining of the data will produce the wrong answer for

2 + 5 * 3

This should still result in 17, but unless you remember the priority rule, you will get the wrong answer.

Since this simple exercise is to construct a simple calculator, limiting it to two values is a reasonable first approximation. Handling this priority issue is left as an exercise for the reader, but obviously you have to find a way to perform multiplication and division first. Most likely, you will use the Python eval() function.

Using a Dictionary to choose operations

One other clever trick you might want to consider using is finding a way to choose the function to be carried out based on which of the operation buttons you select. You can use a simple dictionary which returns the function you want to carry out:

funcs = {"+": self.addfun,
         "-": self.subfun,
         "*": self.multfun,
         "/": self.divfun
         }
 

Then, you can use the text of the operation sign to get the right function:

func = funcs.get(sign) # will be one of four symbols
sum = func()           # execute that function

Conclusions

By deriving new classes from the Button class, you can make a really nice-looking calculator interface in a grid layout of 6 x 4, where the top line is the label and the 5 x 4 represents the control and numeric buttons. All of the buttons benefit from having a Command interface, and all of the numeric buttons can call the same routine since they all put numbers into the top label. The Dot button differs only in that you must limit it to one decimal point per number.

The major class for handling all these button clicks is called a Mediator, and it is the only class that has access to the top data line display.

You keep the various numbers and operations that the user enters in an array or stack, and you can use the dictionary method to selection the function to carry out the right operation. Carrying out computations of more than two numbers means remembering My Dear Aunt Sally to choose the operations to carry out first.

Advertisement
Rock, Paper, Scissors in Python: Part III – Improving the GUI

Rock, Paper, Scissors in Python: Part III – Improving the GUI

In our previous article, we developed a simple Python tkinter user interface for a rock[paper-scissors game. However, if you tried the code, you might stumble on one infelicitous problem:

No buttons selected

The program comes up with no boxes checked, but if you click on the Play button, the program plays a game anyway, assuming that Scissors had been selected. Why does this happen?

When we set up the 3 radio buttons we set the IntVar to -1 so that none of the buttons would be selected.

ChoiceButton.gvar = IntVar()
ChoiceButton.gvar.set(-1)   # none selected
ChoiceButton(lbf, "Paper", 0).grid(row=0, column=0, sticky=W)
ChoiceButton(lbf,"Rock", 1).grid(row=1, column=0,sticky=W)
ChoiceButton(lbf, "Scissors", 2).grid(row=2,column=0, sticky=W)

But let’s see what happens when our code checks the index to see which button is selected:

# play one game
def playit(self):
    index  = int(ChoiceButton.gvar.get())
    self.play = Player.moves[index]

The index returned is -1 as expected. And the three moves are in a tuple:

moves=("P", "R", "S")   #tuple of legal play characters

 And what do we get when we ask for

Player.moves[-1]

Python is unusual among common computer languages in that indexes of less than zero in arrays (lists), strings and tuples mean start at the end and work backward. So the index of -1 gives us the character ‘S’ and -2 would get us ‘R’ and so forth. This also rotates around so that -4 would give us ‘S’ again. In other words, Python apparently takes the index modulo the length of the array so that negative indexing never gives us a  out of bounds error. If we had set the gvar to 3 or more, no radio button would have been selected, and an error would occur.

So, how do we prevent this infelicitous behavior, where a player can click on the Play button when no radio button has been selected?  It would be possible to trap this out-of-range indexes and prevent play from taking place, but since the program assumes that only legal plays can occur, this would take a good deal of careful checking.

Instead, we do what we should have done in the first place and disable the Play button until a radio button has been clicked.

playButton['state']=DISABLED

But how do we turn the button back on?  We recommend using a Mediator.

The Mediator Pattern

The Mediator Design Pattern is a really simple little software trick that keeps classes from having to know about the internal workings of other classes. And when you are dealing with GUI widgets, there might be a number of GUI classes that needn’t know about each other.

Instead, we create a Mediator class and tell it about the push button and the radio buttons, and let it do the work. The Mediator keeps a reference to the pushbutton and receives a call when a radio button is clicked.  The whole thing is just a few lines of code:

class Mediator() :
    def __init__(self, button):
        self.button = button    # save the button reference    

    # called when a radiobutton is selected
    def choiceClick(self):
        self.button['state']=NORMAL # enable the button

So, how do we go about connecting up this Mediator? We create the button and then create the Mediator, passing it a reference to that button:

playButton = Button(root, text="Play", command=self.playgame)
playButton.grid(row=3, column=0, pady=20)
playButton['state'] = DISABLED
med = Mediator(playButton)     # create the Mediator 

So, now the Mediator knows about the button. What about the radio buttons? You may recall that we have derived a ChoiceButton class so it can contain the gvar variable. All we need to do is pass the mediator reference to each button as we create it:

ChoiceButton(lbf, "Paper", 0, med).grid(row=0, column=0, sticky=W)
ChoiceButton(lbf, "Rock", 1, med).grid(row=1, column=0, sticky=W)
ChoiceButton(lbf, "Scissors", 2, med).grid(row=2, column=0, sticky=W)

And the ChoiceButtpm itself creates a command that calls choiceClick in the Mediator:

class ChoiceButton(Radiobutton):
    gvar = None
    def __init__(self, rt, label, index, med):
        super().__init__(rt, text=label,
                         variable=ChoiceButton.gvar,
                         command=self.comd,
                         value=index)
        self.med = med  # Save the Mediator reference

    def comd(self):
         self.med.choiceClick() #call the Mediator

So anytime a ChoiceButton is clicked, it calls choiceClick and that enables the Play button.

Summary

So, to summarize, we use the Mediator to take care of classes that need to send each other information. In this case, clicks on the radio buttons tell the Mediator to enable the Play button. Before that it is disabled. In general, the Mediator is an excellent solution when two or more classes need to tell each other about click events and the like and prevents you having to write GUI classes that know about other GUI classes, and thus getting entangled.

Code for this and the previous two articles can be found on GitHub at jameswcooper/articles

Improving the Radiobuttons in Python Qt5

Improving the Radiobuttons in Python Qt5

PyQt5 is an alternative GUI interface for Python that you can use instead of Tkinter. Both systems provide ways to create buttons, listboxes, tables, checkboxes and radiobuttons. PyQt5 has a number of advantages, though, including built-in Tooltips. Coding for PyQt is in general as easy or easier than for tkinter, but there are some quirks.

One place where you might find it more troublesome is in the way that it handles Radiobuttons. So, in this article, we show you how to make the QRadioButton class a little friendlier.

Now, the idea of a Radiobutton is that you can only select one button, just like old car radios. This interface is now displayed on some screen in your car, rather than by actual push buttons. But the idea is that if you pick one, any other selected button is unselected.

If you have more than one group of Radiobuttons on a page, you want to find a way to group them so that clicking on a member of one group doesn’t affect the other group. In Tkinter, you do this by associating all the members of one group with a single external variable. Then, whatever button you click changes the value of that variable. So, if you have three buttons, the variable might take on the value 0, 1 or 2.

In Qt5, you group the variables by putting them inside a frame or Groupbox. And how do you find out which on was clicked? You have to run through them all to look for which one’s isChecked() status is true. Now, if there are only two buttons this is simple: you only need to check one button. If its status is false, then the other one must be true. 

But what if you have six or more buttons like in this interface for storing cast members in an operetta?

Figure 1: Six radio buttons used in generating a cast table.

In the tkinter approach, you just take the value of that external variable. In PyQt5, you would have to run through them individually or put them in an array (or List) and run through that.

But here is where we have a cooler solution. The QRadioButton is a first class object and you can create derived classes from it really easily. So, all we need to do, is create a RlRadioButton derived from QRadiobutton which contains an index value for each instance of the button. So, we could write

Lead = RlRadioButton(“Lead”, 0)
MinorLead = RlRadioButton(“Minor lead”, 1)

And so forth.  We can then keep the index of the each button in an instance variable: self.index.

class RlRadioButton(QRadioButton):|
    clickIndex = 0    # key of last button selected stored here

    def __init__(self, label, index):
        super().__init__(label)
        self.index= index

Note that the variable clickIndex is a class-level variable There is only one copy of this variable, shared by all six instances of the RlRadioButton class. But how does this variable get set?

It gets set when you click on that RadioButton. We connect the click event for each button to the same method within the button class.

self.toggled.connect(self.onClicked) #connect click to onClicked

The toggled event occurs whenever you click on a button. The event occurs on the button you click on AND on the button which becomes deselected. So, you must check to see whether the button is selected. If it is selected, this method copies the index of that button in that instance into the class variable clickIndex.

#store index of selected button in class variable
def onClicked(self):
    radio = self.sender()
    if radio.isChecked():   #if it is checked, store that index
       
RlRadioButton.clickIndex = radio.getIndex()

So, what is happening is that there are six instances of RlRadiobutton, one for each button. Each instance has a different index number, and if the button for that instance is clicked, it copies its index into the class variable clickIndex they all hold in common. Then, to find out which was selected you simply check the variable RlRadioButton.clickIndex from anywhere in the program.

We illustrate these instances of the RlRadioButton in Figure 2 below, where button 1 was selected.

Figure 2: Three instances of the RlRadioBtton class, showing that they all have access to the same clickIndex class variable.

This shows that while there are three instances of RlRadioButton with three different indexes, there is only one copy of clickIndex that all instances of the RlRadioButton class share.

In Figure 1, you can click on the Status button to see which Radiobutton was selected. The program then fetches that value from RlRadioButton.clickIndex and displays it in a message box using this somewhat verbose message box code:

msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Role index: "
        + str(RlRadioButton.clickIndex))
msg.setWindowTitle("Status")
msg.setStandardButtons(QMessageBox.Ok )
retval = msg.exec_()

and displays the result in that message box.

Figure 3: The status message box.

So, to conclude, the best way to query a large list of QRadioButtons is by deriving a class which can save the current index and asking the class for the index of the last selected button.