We’ve been learning parts of Python together. So far we’ve learned the basics of Python and setting up classes and instances in it. We have a good start on using UI elements and styling them with the basics of tk and ttk. I’d like to make something more real than a demo program with the knowledge gained so far, but have some fun with it. We are going to make a Penguin Spotting Application Popper’s Penguins, named for the 1938 book Mr. Popper’s Penguins by Atwater and Atwater that got me obsessed with penguins. Since the Raspberry Pi works in Linux, there is a bit of an attachment to penguins as well. Therefore we will inventory both real and fictional penguins like Tux.
Popper’s Penguins will do the following:
We will create a GUI Interface for this. But before we do, we have a need for reorganization. For demonstration purposes we’ve used a flat code structure. To organize better, we’ll use a object-oriented approach to our code.
A very common programming pattern is MVC, which stands for Model-View-Controller. MVC basically breaks an application into three parts:
MVC is the core pattern of all Xcode developed apps for iPhone and Mac. As far back as NextStep, development environment used Interface Builder to make a view in a completely separate file from everything else. The developer would build the model in another file and then hook up the controller in a third file to both. The equivalent can get complicated when purely coding. In Python and Raspberry Pi, there’s no interface builder, and that makes things complicated in code. For this lesson I’m leaving out a few things out to ease into the concept in code. I’m going to keep the controller and the view together for now, but will split them out eventually.
In practical terms, the controller will be a set of methods that performs an action when we have something change on the view. We will have a set of methods, which I’ll refer to as handlers or target actions. These will react to events on the view.
Make a new file and add this code:
#Poppers_Penguins_01 #Basic Object Oriented GUI from tkinter import * from tkinter import ttk class MyView(Frame): def __init__(self, parent): self.frame = Frame.__init__(self, parent, background="white") self.parent = parent self.parent.title("Popper's Penguins") self.labelText = StringVar() self.labelText.set(self.parent.title()) self.loadView() #loading the view def loadView(self): #label status_label = Label(self.frame, textvariable = self.labelText) status_label.configure(font=('Sans','36','bold'),background = 'blue', foreground = '#eeeeff') status_label.grid(row=0,column=0,columnspan=3,sticky=EW) def main(): root = Tk() app = MyView(root) root.mainloop() if __name__ == '__main__': main()
This creates a frame and a label. We built a class
MyView which will display the view. It initializes the view. As part of that initialization runs the
loadView() method which draws the view. When we start, our label will have the same text as the title of the window.
Now let’s add two buttons:
add_button = ttk.Button(self.frame,command=self.addPressed,text = 'Add') add_button.grid(row = 2, column = 0) quit_button = ttk.Button(self.frame, command = self.quitPressed, text = 'Quit') quit_button.grid(row = 2, column = 2)
Tkinter uses the
command attribute to set up target action notifications. The code above has two target actions. Let’s now define the handlers for those.
#Handlers -- our pseudo-controller def addPressed(self): self.labelText.set('Penguin Added') def quitPressed(self): self.labelText.set('Quitting') self.parent.destroy()
Build and run. The buttons now work. In earlier scripts we use
quit in our code to quit. In IDLE, that causes the entire window to exit. Here we are being a bit more gentle, removing the window from the view with
destroy() in our code.
Let’s now style our buttons. Add this method above
#Style Sheet def makeStyle(self): self.s = ttk.Style() self.s.configure('TButton',background = 'blue', foreground = '#5555ff', font = ('Sans','14','bold')) self.s.configure('TLabel',font=('Sans','36','bold'),background = '#5555ff', foreground = '#eeeeff')
Add the new method to
__init__() for the
ef __init__(self, parent): self.frame = Frame.__init__(self, parent, background="white") self.parent = parent self.parent.title("Popper's Penguins") self.labelText = StringVar() self.labelText.set(self.parent.title()) self.loadView() self.makeStyle()
Change the label to a
ttk.Label and remove the
.configure with a comment:
status_label = ttk.Label(self.frame, textvariable = self.labelText) #status_label.configure(font=('Sans','36','bold'),background = 'blue', foreground = '#eeeeff')
Add a style map for the buttons:
self.s.map('TButton', foreground = [('hover','#5555ff'), ('focus', 'yellow')]) self.s.map('TButton', background = [('hover', '#eeeeff'),('focus','orange')])
Our mapping for the button is slightly different from our last post. There are two states
focus. Before we used
Build and run.
The display look as expected. Move your mouse over the add button.
It changes as the style map dictates. Click the button then move the cursor away from the button.
The button now has focus shown by the orange background. For mouse actions, focus is the last button clicked. For keyboard actions, which we haven’t covered yet, focus allows the keyboard space bar or enter key to do the same as a click.
In our next lesson, we will add a few more UI elements. We will add a dialog box for quitting and add our input widgets.