Category: Python

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.

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

Rock, Paper Scissors in Python: Part II,  Building a GUI

Rock, Paper Scissors in Python: Part II, Building a GUI

In our first article, we designed a Rock, Paper, Scissors game that works from the keyboard. It printed out messages and shows the total score when you quit. But even a game this simple will benefit from a graphical user interface (GUI). Here we show how to use the Tkinter toolkit to create that simple interface. Specifically, we are going to use the tkinter.ttk toolkit, because it has a nice LabelFrame widget to hold the three RadioButtons.  It looks like this:

You can select the three possible plays from the radio buttons in the “Plays” LabelFrame, and then click on the Play button to have the program player select its move and tell you who won.  The messages from the program are shown in the Listbox on the right, showing what the Auto player has selected, and who wins. The total score for the two players is shown in the blue label above the Listbox.

Creating the GUI

For simple programs like this, we usually create a Builder class with a build method and a few other methods as needed. Here the only method we’ll need is the playit method, that triggers one round of play.

 Here is the program’s outline:

import tkinter as tk
from random import randint
from tkinter import *
from tkinter.ttk import *

class Builder():
    # play one game
    def playgame(self):
        # players play game

    # create window     
    def build(self):
       root = tk.Tk()
       root.geometry("300x200")
       root.title("Rock paper scissors")
        #UI creation code   
        mainloop()     # start the window

# Program starts here
if __name__== "__main__":
    bld = Builder()
    bld.build()

Adding GUI Controls

We use the Grid layout to arrange the GUI widgets, with the LabelFrame and Play  button in column 0 and the score label and Listbox in column 1. The three radio buttons are inserted into the label frame inside their own three line grid.

The only sneaky part is the way the Radio Buttons work. All of radio buttons in a group use the same variable to tell you which button has been selected. That variable is an IntVar, an integer wrapped in an object that is required by the underlying tk toolkit. So, when you create each Radiobutton, you have to pass it a reference to that IntVal variable, and the value that that button will pass to the IntVar, usually small integers, such 0, 1 and 2.

The design question we have to solve is where that IntVar variable will be located so each of the RadioButtons can access it. The obvious place is right inside the radio button code itself. So we derive a new ChoiceButton from Radiobutton that contains the class level variable gvar. Now all instances of the ChoiceButton have access to that same variable, and can change it to 0, 1 or 2 depending on which button is clicked. Here’s the whole new ChoiceButton class.

class ChoiceButton(Radiobutton):
    gvar = None  # will be set to an IntVar

    def __init__(self, rt, label, index):
        super().__init__(rt, text = label,
                         variable =ChoiceButton.gvar,
                         value=index)
        self.text = label
        self.index=index

When we create the GUI, we will set that gvar to an IntVar. The complete GUI code is

def build(self):
    root = tk.Tk()
    root.geometry("300x200")
    root.title("Rock paper scissors")
    # create a label frame for the radio buttons
    lbf = LabelFrame(root, text="Plays")
    lbf.grid(row=0, column=0, rowspan=3, pady=10, padx=10)
    
# create 3 radio buttons, but set none as 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)

    # create Play button - calls playgame method
    playButton = Button(root,text="Play", command=self.playgame)
    playButton.grid(row=3,column=0,pady=20)

    # create score label
    self.scoreLabel = Label(text="scores", foreground="blue")
    self.scoreLabel.grid(row=0, column=1)

    # create listbox
    self.mesgList = Listbox(root, width=30)
    self.mesgList.grid(row=1, column=1, rowspan=3)

    # create two players
    self.player1 = Player("You")
    self.player2 = AutoPlayer("Auto", self.mesgList)
    mainloop()

Note that we use the same Player and AutoPlayer classes as in the previous example;. The only difference is that the AutoPlayer adds a message to the Listbox instead of printing it on the console:

class AutoPlayer(Player):
    def __init__(self, name, list):
        super().__init__(name)
        self.list = list

    def playit(self):
        playval = randint(0, 2)
        self.play = Player.moves[playval]
        self.list.insert(END, self.name + " selects " + self.play)
        self.list.yview(END)

The Player class differs only in its playit method, which obtains the selected Radiobutton from the index in gvar. Note the yview(END) method on the Listbox, which scrolls to the bottom of the list so that the last line always shows.

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

And the playGame method of the Builder class differs only in that the winner is added to the Listbox, and the label is updated to show the current score:

def playgame(self):
   self.player1.playit()    # read buttons
   self.player2.playit()    # calc random play  
# compute winner
   winner = Winner(self.player1, self.player2)
   self.mesgList.insert(END, winner.findWin())  # print winner
   self.mesgList.yview(END) #move to bottom of list 
# show current score
   self.scoreLabel.config(text="You: "
                            +str(self.player1.wincount)+"--- Auto: "
                            +str(self.player2.wincount))

Conclusion

The differences in the console game and the GUI version are pretty small. The Player class gets your play from the radio button selected, and all the messages are added to the Listbox. The running score is changed in the scoreLabel each time.

All of the code for this example can be found in Github at jameswcooper/articles/rockpaper

Rock, Paper, Scissors Game in Python: Part I

Rock, Paper, Scissors Game in Python: Part I

You probably have played the little hand game Rock, Paper, Scissors one in a while. Essentially two players bring up a fist, a flat hand ot two fingers to represent rock, paper or scissors. The winner is based on these three simple rules:

  • Rock breaks scissors
  • Scissors cuts paper
  • Paper covers rock

In the case of a tie, no one wins.

This little game is easy to create in Python using the keyboard to enter R, P or S, or Q to quit. The second player is the program itself which uses a random number generator to choose one of the three outcomes. This little game is sometimes given as an early assignment, but beginners usually miss some of the really clever features of Python that can make the game more elegant and succinct. We’ll illustrate tuples, classes and subclasses, and dictionaries in this simple article.

We can start by using a tuple to represent the three possible moves:

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

These are the only three characters that the player should be allowed to enter. So we can create a simple entry loop that repeats until one of those three characters is entered. We use the upper method to make sure the entry is uppercase. Note that the code “play in moves” returns True if the character entered is P, R or S and otherwise returns False.

legal = False
while not legal:
    play = input("enter play (P, R, S): ").upper()
    legal = play in moves 

 And, if you want to allow the player to enter “Q” to quit the game you could check for that Q and set a quit flag:

def playit(self):
    legal = False
    while not legal:
        play = input("enter play (P, R, S): ").upper()
        if play=="Q":
            quit = True
        legal = play in Player.moves or quit

 As you can see, this is now a function.  But why not make it part of a class?

You may recall that classes are a major part of Python. Classes are components that can contain data, and can exist in many instances with differing data in each.

A Player class

Our complete player class has the name of the player and a play variable containing the latest play. It also keeps the state of the quit flag and the legal flag.

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

    def __init__(self, name):
        self.name = name    #initialize name,quit, and count
        self.quit = False
        self.wincount = 0   #counter of wins

    def playit(self):
        legal = False
        while not legal:
            self.play = input("enter play (P, R, S): ").upper()
            if self.play=="Q":
                self.quit = True

            legal = self.play in Player.moves or self.quit
 # the number of wins is counted here
def countWin(self):
    self.wincount += 1
 

So, as you can see, the Player class has a playit method which sets the play variable to P, R or S. It also can be set to Q, and the quit flag indicates that this is not a real play. Note that the moves tuple is a class variable. This means it is part of all classes of this type and you can access it by Player.moves.

But, what about the other player? It is the computer program itself, and it uses the randint method to select 0, 1 or 2 and these indices indicate which of the three plays has been choses. So, we need another class like Player for the computer player.

As you may recall, you can derive new classes from old ones, so we create an AutoPlayer class derived from Player. It only contains the new playit method. All the other variables and methods are in the base Player class. So, our simple AutoPlayer class is just

class AutoPlayer(Player):
    def __init__(self, name):
        super().__init__(name) # pass the name into the parent class

    def playit(self):
        playval = randint(0, 2)     # select 0, 1 or 2
        self.play = Player.moves[playval] # get that play from the tuple
        # print out what it has selected
        print(self.name + " selects " + self.play)

So, now, we see how we can write a loop for you to play the game:

player1 = Player("You")
player2 = AutoPlayer("Auto")
while not player1.quit: #loop until a 'Q' is entered
    player1.playit()
    if not player1.quit:
        player2.playit()

Picking a Winner

But how do we know who won? This is really simple, since there are only 3 cases (plus the tie) to check for.  We can create a Dictionary of these cases and the resulting message you print out:

game= {"RS": "rock breaks scissors",
       "SP": "scissors cuts paper",
       "PR": "paper covers rock"
       }

We only need to check for ties and check for who wins, by combining the string for the two players.We do all this within a Winner class which then returns the outcome:  First we initialize the variables:

class Winner():
    game= {"RS": "rock breaks scissors",
           "SP": "scissors cuts paper",
           "PR": "paper covers rock"
           }

    def __init__(self, p1, p2):
        # copy variables into class
        # to make this simpler to read
        self.p1 = p1
        self.p2 = p2

Note that the game dictionary is also a class variable.

Then, the findWin method checks for ties and if there is no tie, it calls checkWin to check if p1 beat p2 or p2 beat p1:

# A winner matches on of the three
# game dictionary entries abouve
# we check p1+2 and p2+p1
def checkWin(self, p1, p2):
    # here are the two outcomes
    # for both player orders
    match1 = p1.play + p2.play
    match2 = p2.play + p1.play
    mesg = Winner.game.get(match1)
    if mesg != None:
        p1.countWin()
        outcome = p1.name + " win -- "+mesg
    else:
        mesg = Winner.game.get(match2)
        p2.countWin()
        outcome =  p2.name + " win -- " + mesg
    return outcome

def findWin(self):
    if self.p1.play == self.p2.play:
       return "Tie, no winner"
    else:
        return self.checkWin(self.p1,self.p2)

Note that Player class contains a countWin method that increments the winner count of that player won. Then, when you finally type ‘Q’ the program can print out the final score.

player1 = Player("You")
player2 = AutoPlayer("Auto")
while not player1.quit: #loop until a 'Q' is entered
    player1.playit()
    if not player1.quit:
        player2.playit()
        # compute the winner
        winner = Winner(player1, player2)
        print(winner.findWin()) # print winner

# print out the wins for each player
# when you type "q" for quit
print(player1.name, player1.wincount)
print(player2.name, player2.wincount)

The final output

Here is how the program works:

enter play (P, R, S): r
Auto selects R
Tie, no winner
enter play (P, R, S): r
Auto selects S
You win -- rock breaks scissors
enter play (P, R, S): q
You 2
Auto 2

Summary

We used the tuple to check for legal input (P,R or S) and then created a Player class which has a playit method and contains a play variable which contains the charact representing that play. Then we created a derived AutoPlayer class which has the same internal variables and methods, but where the playit method uses a random number function to generate 0, 1 or 2. These are then indexes into the tuple  to find the character representing that play.

Finally, we created a dictionary of the three possible plays and the message describing the winning cases and used the Winner class to find if p1+p2 won or id p2+p1 won. Thus, in this simple example we used the tuple, the dictionary, classes and a derived class to write a simple game program.

The complete code for this and the programs in the following articles is available at GitHub under jameswcooper/articles

Entering data in Python: console or GUI

Entering data in Python: console or GUI

Suppose you want the user to enter some data for a Python program to work on. For this, Python provides the input statement, which can print out a prompting string and wait for keyboard input. 

name = input("What is your name? ")
print("Hi "+name +" boy!")

This little program asks for your name and waits for you to type a string and press Enter. The input statement accepts keyboard input and returns a string. So, the resulting console results might look like this:

What is your name? Jim
Hi Jim boy!

Heinlein fans may recognize the reference.

You can paste that 2-line program into any development environment, such as Thonny, PyCharm or even Jupyter Notebook and see it work right away.

Adding two numbers

Of course, you can enter numbers as well, but you must be sure to convert the resulting strings into an int or a float. Here we use the more general float.

x = float(input("Enter x: "))
y = float(input("Enter y: "))
print("The sum is: ", x+y)

The resulting output is:

Enter x: 23.45|
Enter y: 41.46
The sum is:  64.91

Of course, this naïve program expects only legal input. If you enter, say, “qq” instead of 22, you will get a Python error:

File "C:\Users\James\PycharmProjects\input\inputdemo.py", line 7, in <module>    
y = float(input("Enter y: "))
ValueError: could not convert string to float: 'qq'

There are ways to check for this, of course, such as catching Exceptions, and we will explain those in the following example.

However, you will only find the input statement in rudimentary examples. Most programs that need user input get it from a windowing interface. We’ll show these same examples next using the tkinter GUI library.

Making a windowing example program

You can do much the same thing by using the Entry field in the tkinter GUI (graphical user interface). Here we repeat those examples in simple tkinter windows.  To use tkinter, you have to import that library into your program:

from tkinter import *
import tkinter as tk

You also create an abbreviation for tkinter, naming it just tk.

Then, most of the setup code creates the visual widgets and their arrangement. In the following example, we use an Entry field, a Button and two Labels. You start by getting the window system pointer tk.Tk() and use it to attach the widgets to. After you create this objects, you run the mainloop() function which displays the window and receives mouse and keyboard input. The window keeps running until you close it by clicking on the “X” in the upper right corner or selects some widget that causes the window to close.

Our first example above was one where you enter your name and it says Hello back, again giving homage to Heinlein in the process. The tkinter program does the same thing

The code for this program creates the two labels, the Entry field and the OK button:

def build(self):
    root = tk.Tk()
    # top label
    Label(root,
         text="""What is your name?""",
         justify=LEFT, fg='blue', pady=10, padx=20).pack()

    # create entry field
    self.nmEntry = Entry(root)
    self.nmEntry.pack()   # and put it into the window

    # OK button calls getName when clicked
    self.okButton = Button(root, text="OK",
                            command=self.getName )
    self.okButton.pack()

    # This is the label whose text changes
    self.cLabel = Label(root, text='name', fg='blue')
    self.cLabel.pack()
    mainloop()      #run loop until window closes

When you click on the OK button, it calls the getName method, which fetches the text from the entry field and inserts it into the bottom label text.

# gets the entry field text
# places it on the cLabel text field
def getName(self):
   newName = self.nmEntry.get()
   self.cLabel.configure(text="Hi "+newName+" boy!")

Adding two numbers

And our second example rewritten from above reads two Entry fields, converts them to floats and puts the sum in the lower label.

 The code for this window is much the same, except that we fetch and add two numbers. Here is the function the OK button calls.

xval= float(self.xEntry.get())
yval = float(self.yEntry.get())
self.cLabel.configure(text="Sum = "+str(xval+yval))

Catching the error

However, if you enter some illegal non-numeric value, you can catch the exception and issue a error message:

try:
    xval= float(self.xEntry.get())
    yval = float(self.yEntry.get())
    self.cLabel.configure(text="Sum = "+str(xval+yval))
except:
    messagebox.showerror("Conversion error",
                              "Not numbers")

If you enter non-numeric strings, the program displays this message box:

And that’s all there is to creating a program with a simple graphical user interface.

Comparing Python Tkinter and PyQt5

Comparing Python Tkinter and PyQt5

TkInter is the well-known GUi system based on the Tk toolkit and interfaced to Python so that it has become the preferential GUI interface system. PyQt5 is an analogous user interface developed and support by Riverbank Computing, and is essentially a set of bindings for the QT platform developed by the Qt company. PyQT5 is available under a GNU GPL v3 license, or as a commercial license. This essentially means that you can use PyQt5 freely, but you must distribute your source code with any product you build using PyQt5. Or, you can buy a commercial license.

GUI using Tkinter

GUI using PyQt5

The two figures above show the same interface developed in Tkinter and PyQt5. They show that you can build pretty much the same kind of GUI with either system.  I spent a week learning PyQt5 and building the new interface to match the one I had already built in Tkinter. Each system has different advantages and disadvantages, and we’ll summarize them in the article that follows.

The tkinter version is fully integrated with the database, but I only bother to connect the right hand searchbox and listbox to the database in PyQt5. The rest are just hardcoded,

But basically, the systems do about the same things and are about equally easy (or hard) to use, and you can’t go wrong using either of them. There was no clear winner of this experiment: once you have climbed the learning curve you can build nice-looking systems either way.

I built the Tkinter version of this interface as part of a larger development project to interface our opera company’s data to a new MySQL database, where building a UI gave us the ability to view the information in more flexible ways. This screen represents a way to create a cast list for the current and previous shows and generate spreadsheets of those casts for the Board , the cast and the directors to work with. The left-hand table is the final cast, sorted by role-type and sex, which amounts to one simple database query.

It allows you to select any person in our database and assign them a role in the current or an older production.

I learned how to use PyQt5 from the on-line PyQT5 reference guide as well as from this short tutorial by Michael Herrman. He has also written a book on PyQt and there is a link to it at the bottom of the tutorial.  The Zetcode tutorial was also helpful. There is also a video tutorial at learnpyqt.com.

If you want to build a GUI using listboxes, buttons and checkboxes, you won’t have any trouble with PyQt. In fact, since the listbox automatically includes a slider, you will find it a bit easier.  It is also worth noting that all PyQt widgets have Tooltips: helpful phrases that can explain what a widget is for.

I already noted that the QRadioButtons work better if you derive a class that holds the button index or title, using a Mediator pattern.

Layout managers

Tkinter has two major layout managers, pack() and grid(). Pack arranges the objects in the frame you provide. Grid allows you set up an n x n  grid and place the objects in one or more grid cells.

PyQt5 has three layout managers: QHBoxLayout, QVBoxLayout and QGridLayout. You can add widgets to the box layouts and they will line up horizontally or vertically. The QGridLayout is similar to, but not the same as, the tkinter grid. The biggest single difference is that if you place a widget in a grid cell, it expands to fill the entire cell. In order to put a button in a grid and not have it stretched to fill the cell, you have to add a QHBoxLayout inside the cell and then perform a hbox.addStretch(1) before and after the button to center it. These are essentially spacers that grow to fill the space on either side of the widget.

QtDesigner

PyQt5 provides the QtDesigner app which allows you create layouts visually. It actually includes layouts, and you can at least look at what code it generates. However, the resulting file is of type .ui and you have to run the pyuic5.exe program to convert the .ui file to a Python file. Once you have done this and edited the Python file, you can never go back to the designer.

Events in Qt5 are referred to as signals and slots, where the event is a signal and the callback function is a slot.  You can easily write analogous programs in PyQt5 to handle events much as you do in tkinter. You can also do this in the QtDesigner, but the event interface in the designer is a lot of trouble to use. Writing the code yourself is easier, and you probably would want to modify the code anyway. And, again, once you change the code, you can’t do back to the designer.

PyQt5 Style Sheets

While PyQt5 widgets have a plethora of useful methods, the designers left out such things as changing colors and borders. For this you have to resort to CSS (Cascading Style Sheets).  You can find a whole web page of Qt5 CSS examples here.

For example, suppose you want a label to be blue instead of black. In tkinter, you would write:

lb1 = Label(cFrame, text="Character", foreground='blue')

But in PyQt5, you would have to write.

lb1 = QLabel("Character")
lb1.setStyleSheet("QLabel { color : blue; }")

This sets the style for all instances of QLabel, but you can specify an individual label as well.

A more difficult case was setting the border of the GroupBox (which is the same as a tkinter LabelFrame). I also changed the color of the text label here too.

self.app.setStyleSheet("""QGroupBox {border:1px solid black;
    margin-top: 10ex;  padding: 15 3 px; }""")
self.setStyleSheet("QGroupBox::title {"
                     "color: blue;"
                    "background-color: transparent;"
                    "padding-top: -50px;"
                    "padding-left: 8px;} ");

Setting these style sheets can be tricky because you do not get any errors if you leave something out, like that terminal semicolon.

Listboxes and Model-View-Controller

PyQt5 has a list box, a table widget and a separate Treeview widget. While each of them can be used as stand-alone widgets, they also can be used in the Model-View-Controller system, where the data is the Model, the widget is the View and the Controller is the user or some external event. Essentially this is useful when the data changes frequently and this will cause your table to be refreshed automatically. I haven’t tried this out yet, as it takes quite a bit of programming. However, there are list and table widgets you can use without getting into using MVC.

I found the table display troublesome, because while you could remove the gridlines and left hand column numbers, the lines were quite widely separated.  After consulting the informants on Stackoverflow, I found that you could code the line spacing a line at a time. It turns out that the height of each row is calculated from the top of the table, so for each row, you have to calculate it as follows:

self.castTable.setRowHeight(row, (row + 2) * 7)

It turned out, however, that the spacing in the Treeview looks a lot better, and I am switching to that.

Conclusions

Tkinter is better documented and may a bit easier to work with. But there are more widgets in the PyQt5 library, including date editors, a progress bar, a slider, a font picker, a toolbar, and a multiline text entry field. It also supports the MVC pattern and includes Tooltips, which might be helpful for new users. While Herrman felt there was a difference in the clarity of the widgets, I didn’t notice it.

PyQt Radio Buttons and the Mediator Pattern

PyQt Radio Buttons and the Mediator Pattern

In our previous article, we showed how to derive a new class from QRadioButton that keeps the index value of that button and keeps the click event callback right in the class itself.

In this simple article we are going to look at an easy program that fills an entry field with a text string based on which radio button you select. We also introduce you to the Mediator Design Pattern to help communication between widgets.

We came up with this little trick while writing a program to create a cast list for our operetta company. Leads play named roles, but for chorus member we just store their voice part. The radio buttons are labeled S, A, T and B, and when you click on one of them, the entry field is filled with the full name of that voice part: Soprano, Alto, Tenor and Bass. Since the entry field is editable, you can add more text like “2nd Tenor” where this might be useful, and that name is stored as part of the last list. 

Using a Mediator

When you click on any of the RadioButtons, the long name is copied into the entry field. The question is, how does it get there?  In effect, each of the four button instances need to communicate with the entry field. And, while that is not difficult to achieve, it doesn’t scale very well as your program grows to include more visual widgets. To simplify this problem, you use the Mediator Design Pattern and the Mediator class.

Rather than the various controls all sending information to each other, the Mediator class becomes the traffic cop that receives the various button clicks and other widget actions. Then, it knows about the other controls you might want to communicate with and passes the click information on to them. So, when we create instances of the derived vcRadioButton, you pass it the label it displays, the long name it is to send on to the entry field and a reference to the Mediator class:

You first create the Mediator and give it a reference to the entry field  (which is called QLineEdit in PyQt5):

# create the entry field
self.entry = QLineEdit()
grid.addWidget(self.entry,0,0)

# Create Mediator and
# pass it the entry reference
self.med = Mediator()
self.med.setEntry(self.entry)
# Create a GroupBox for the four radio buttons
self.voiceBox = QGroupBox("Voice part")
vcGrid = QGridLayout()

Then you create the buttons and pass each of them a reference to the Mediator

# create the four radio buttons and labels
vs = VcRadioButton("S", "Soprano", self.med)
va = VcRadioButton("A", "Alto", self.med)
vt = VcRadioButton("T", "Tenor", self.med)
vb = VcRadioButton("B", "Bass", self.med)


# and add them to the 2 x 2 grid
vcGrid.addWidget(vs, 0, 0)
vcGrid.addWidget(va, 0, 1)
vcGrid.addWidget(vt, 1, 0)
vcGrid.addWidget(vb, 1, 1)

Then we add the vcGrid to the voiceBox layout, and the voiceBox to the outer grid.

self.voiceBox.setLayout(vcGrid)
grid.addWidget(self.voiceBox, 1, 0)
self.setLayout(grid)

The VcRadioButton class

Our actual VcRadioButton class is even simpler than the one we wrote for the six radio button example, because we don’t have to store anything in a class variable: we just tell the Mediator that the button has been clicked.

class VcRadioButton(QRadioButton):
    def __init__(self, label, title, med):
        super().__init__(label)
        self.title = title  #save the title
        self.med = med      # and copy the Mediator reference
        # connect the button clicks to the comd method                                                   
        self.toggled.connect(self.comd)

    # returns title stored in this class instance
    def getTitle(self):
        return self.title
    # gets the title and puts it in the character entry field
    # using the Mediator
    def comd(self):
        radio = self.sender()   #get the button you clicked
        if radio.isChecked():   # if checked copy the title
            self.med.setVoice(radio.getTitle())

Note that the VcRadioButton connects the click event (called toggled in Qt5) to the comd method right there in the same class. And that comd method tells the Mediator to set the voice part into the entry field.

Then finally our Mediator is simple, since we are only mediating connections between radio buttons and the entry field. When the radio button is clicked, it calls the setVoice method in the Mediator, which copies the text into the entry field.

# The Mediator saves a reference to the entry field
# and copies text into the field when setVoice is called

class Mediator():
    # save the entry field reference
    def setEntry(self, entry):
        self.entry = entry
    # copy the text into the entry field
    def setVoice(self, text):
        self.entry.clear()
        self.entry.insert(text)
 

This may seem like a lot of running around to copy text into an entry field, but the Mediator quickly becomes quite important when your program needs to handle interactions among a number of widgets, such as list boxes, push buttons and check boxes. It is probably the most useful and significant of the 23 Design Patterns when you are writing user interfaces.

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.