Make App Pie

Training for Developers and Artists

From Apple to Raspberry Pi: Using Object Oriented Programming with Tkinter

appleberryWe’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:

  • Enter data on type of penguin, date, activity and comments.
  • Display data on penguins in different sort orders
  • Display images of the penguin

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 Basic Intro to MVC

A very common programming pattern is MVC, which stands for Model-View-Controller.  MVC  basically breaks an application into three parts:

  1. Model — This is the data you are working with and the real function of the app. As a rule, the Model can be used completely separate from the  rest of the app. For example, I can use the same class in a text-based app or a GUI based app with no change of functionality.
  2. View — This is where the user sees and interacts with the app. It is the widgets, controls and text. Views do not interact in any way with the model.
  3. Controller — Since neither the Model or View can interact, The controller connects the Model and the View together.  It too, however is limited. The Model and View can’t directly change anything in the controller. The controller can send messages to the view and model to tell them to do something, and can observe the change.

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.

Building our View/Controller

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.

Add Buttons with Style

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 loadView()

    #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 MyView class:

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 hover and focus. Before we used active.

Testing Our App so Far

Build and run.

Screenshot 2014-05-15 19.45.39

The display look as expected. Move your mouse over the add button.

Screenshot 2014-05-15 19.45.49

It changes as the style map dictates. Click the button then move the cursor away from the button.

Screenshot 2014-05-15 19.45.00

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.

One response to “From Apple to Raspberry Pi: Using Object Oriented Programming with Tkinter”

  1. Thanks for this article – it’s a great introduction. I’m curious about one thing. On line 8 of your code, you assign self.frame to the output of the Frame init function. The return value of the init function is None, so it can be a little misleading. The variable self.frame doesn’t actually contain a frame. In fact, if you create another variable (self.anything) and assign it to None, you can bind the label on line 18 to self.anything and this will still work. I have no idea why binding the label to any object makes it appear, but it does (maybe someone could chime in on this).

    I think this code would make a little more sense if you initialized a grid layout for the frame by doing self.grid(), and then you can bind the label in line 18 to self (which is the frame with the grid). Line 8 would just be: Frame.__init__(self, parent, background=”white”)

    with no assignments.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.