Make App Pie

Training for Developers and Artists

From Apple To Raspberry Pi: An Example of Implementing a Python MVC (Part 1)

appleberryI gave a template for MVC in the last post, This time I’m going to use it in the Popper’s Penguins application. We’ll re-organize what we already did with views and controllers to match the template.  We’ll add the model in the next installment of this series.

Changing the View

Let’s start by modifying the view. The code in the view controller is dependent on this code.

Change the View’s Constructor

Change the __init__ for the view to this:

#(1) Change parent to vc and add a frame 2014 May 26 a  
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row=0,column=0)
        self.vc = vc
        
        #properties of the view 
        self.labelText = StringVar()
        self.labelText.set("Popper's Penguins Ready")
        self.penguinType = StringVar()
        self.penguinType.set('Penguin Type')
        self.penguinAction = StringVar()
        self.penguinAction.set('Penguin Action')
#(2)remove self.vc here as vc calls this, not the other way around.   2014 May 26
#       self.vc = MyViewController(self)
        self.loadView()
        self.makeStyle()

Lines 3 and 4 creates the frame for the view. We set the pointer to the view controller to self.vc in line 5 which the view controller passes to the view’s constructor when it instantiates the view object. We left lines 8-13 alone. There is a valid point to set them in the view controller, but there is an equally valid point they are initial values, and thus part of the view. In Interface Builder terms, this is the same as setting the default values in the properties window.

Since the view controller instead of the view is the first class called by main() we remove self.vc in line 15. I commented it out here but it perfectly okay to remove it completely.

Remove the Pseudo-Controller

We have in our code the redundant pseudo-controller for target-actions within the view. We want target actions to happen in our controller, not our view. Remove the code or comment it out like this:

Handlers -- our pseudo-controller
#(3)removed from application 2014-May-26
#    def addPressed(self):
#        self.labelText.set(self.penguinType.get()+ ' Penguin '+ self.penguinAction.get() + ' Added')
#    def quitPressed(self):
#        self.labelText.set('Quitting')
#        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
#        if answer==True:
#            self.parent.destroy()

Add Setters and Getters for Our Properties

Next we need setters and getters for the controller to communicate with the view. Add the following methods after the __init__() method:

#(4)added setters and getters for the properties 2014-May 26
    def setLabelText(self,newText):
        self.labelText.set(newText)
    def getLabelText(self):
        return self.labelText.get()
    def setPenguinType(self,newType):
        self.penguinType.set(newType)
    def getPenguinType(self):
        return self.penguinType.get()
    def setPenguinAction(self,newAction):
        self.penguinAction.set(newAction)
    def getPenguinAction(self):
        return self.penguinAction.get()

None of this special. At the moment they are simple setters and getters. We may make some form of type checking for safety at some point, but for simplicity, we’ll skip that for now.

Change the Target-Action Calls

The command attributes are not calling the correct methods. They are calling the target action we just removed. We need to change the calls so they call the controller’s versions.  Change the loadView() for the buttons as follows:

#(5)changed the command= to refer to the view controller 2014-May-26
    # self.addPressed now is self.vc.addPressed
    # self.quitPressed now is self.vc.quitPressed
    #buttons
        add_button = ttk.Button(self.frame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 2, column = 0)
        quit_button = ttk.Button(self.frame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 2, column = 3)

The View Controller

We’ve made the change to the view. Now to make the changes to the view controller. We’ll instantiate the view, make target actions and refer to view properties by setters and getters.

Instantiate the View in the View Controller

Change the constructor first so we have a view to use. Change the __init__ in MyController to this:

    def __init__(self,parent):
        self.parent = parent;               
#(6)added instantiation of view 2014 May 26
        self.view = MyView(self)

Any time we need to use a property of a view, we can now access it from self.view.

Change the Target Actions

We moved the target actions into the view controllers.  We’ll need to set up those in the View Controller.   There are a few place-maker versions already there. Replace them with this:

#Handlers -- target action
    def addPressed(self):
#(7a) Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
        
    def quitPressed(self):
#(7b) Change getters and setters for the view
        self.view.setLabelText('Quitting')
        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
        if answer==True:
            self.parent.destroy() 

In lines 4 and 8, we are now using the setters and getters we made in the view, and not directly using Tkinter set() and get(). Line 11 calls the root node to destroy the window when we indicate in the dialog box to quit.

Change the Main Method

We deleted references to the window in our view constructor. We also have to change what class we instantiate in main() from MyView to MyViewController. It makes a window, sets a title, starts the view controller and starts mainloop(). Change it to this:

def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    root.title("Popper's Penguins")         
    app = MyViewController(root)
    root.mainloop() 
 

Test the Application

Build and run:

Screenshot 2014-05-19 08.54.16

If all works right, nothing will change.  In our next lesson, we’ll discuss a little about lists and will add our first iteration of the  model.

The Whole Code.

# poppers_penguins_MVC_01
#MVC Version 2014 May 26
#implements the View and View Controller according to the MVC template

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
 
 
class MyViewController():
    def __init__(self,parent):
        self.parent = parent;      
         
#(6)added instantiation of view 2014 May 26
        self.view = MyView(self)

#Handlers -- target action
    def addPressed(self):
#(7a) Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
        
    def quitPressed(self):
#(7b) Change getters and setters for the view
        self.view.setLabelText('Quitting')
        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
        if answer==True:
            self.parent.destroy()
 
class MyView(Frame):
#(1) Change parent to vc and add a frame 2014 May 26 a  
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row=0,column=0)
        self.vc = vc
        
        #properties of the view 
        self.labelText = StringVar()
        self.labelText.set("Popper's Penguins Ready")
        self.penguinType = StringVar()
        self.penguinType.set('Penguin Type')
        self.penguinAction = StringVar()
        self.penguinAction.set('Penguin Action')
#(2)remove self.vc here as vc calls this, not the othere way around.   2014 May 26
#        self.vc = MyViewController(self)
        self.loadView()
#       self.makeStyle()
         
#Handlers -- our pseudo-controller
#(3)removed from application 2014-May-26
#    def addPressed(self):
#        self.labelText.set(self.penguinType.get()+ ' Penguin '+ self.penguinAction.get() + ' Added')
#    def quitPressed(self):
#        self.labelText.set('Quitting')
#        answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?')
#        if answer==True:
#            self.parent.destroy()

#(4)added setters and getters for the properties 2014-May 26
    def setLabelText(self,newText):
        self.labelText.set(newText)
    def getLabelText(self):
        return self.labelText.get()
    def setPenguinType(self,newType):
        self.penguinType.set(newType)
    def getPenguinType(self):
        return self.penguinType.get()
    def setPenguinAction(self,newAction):
        self.penguinAction.set(newAction)
    def getPenguinAction(self):
        return self.penguinAction.get()
    

#Style Sheet
    def makeStyle(self):
        self.s = ttk.Style()
        self.s.configure('TFrame',background = '#5555ff')
        self.s.configure('TButton',background = 'blue', foreground = '#eeeeff', font = ('Sans','14','bold'), sticky = EW)
        self.s.configure('TLabel',font=('Sans','16','bold'),background = '#5555ff', foreground = '#eeeeff')
        self.s.map('TButton', foreground = [('hover','#5555ff'), ('focus', 'yellow')])
        self.s.map('TButton', background = [('hover', '#eeeeff'),('focus','orange')])
        self.s.configure('TCombobox',background = '#5555ff',foreground ='#3333ff',font = ('Sans',18))
   
#loading the view
    def loadView(self):
        #label
        status_label = ttk.Label(self.frame, textvariable = self.labelText)
        status_label.grid(row=0,column=0,columnspan=4,sticky=EW)
#(5)changed the command= to refer to the view controller 2014-May-26
    # self.addPressed now is self.vc.addPressed
    # self.quitPressed now is self.vc.quitPressed

    #buttons
        add_button = ttk.Button(self.frame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 2, column = 0)
        quit_button = ttk.Button(self.frame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 2, column = 3)
    #combobox
        penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy']
        penguinType_combobox = ttk.Combobox(values = penguinType_values, textvariable = self.penguinType)
        penguinType_combobox.grid(row =1, column = 0)
        penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute']
        penguinAction_combobox = ttk.Combobox(values = penguinAction_values, textvariable = self.penguinAction)
        penguinAction_combobox.grid(row=1, column = 3)


         
def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    root.title("Popper's Penguins")         
    app = MyViewController(root)
    root.mainloop() 
 
 
if __name__ == '__main__':
    main()  

Leave a comment

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