Make App Pie

Training for Developers and Artists

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

appleberryIn the last part we implemented the view and controller for the Popper’s Penguins application. Now we add the model.  Along the way, we will make a delegate method for the model and change our target actions to add our data to a list.

Add the Model Class

We want to keep a list of our penguin observations. As a beginning data structure, the list data structure in Python will do this. Lists are mutable collections of sequential data. Lists can take any data type, including more lists. This will come in handy for us. We have two pieces of data we want to keep together: the penguin type and the penguin action.  We can then send that list to the list in our model.

Add this code:

#(1)adding the model 2014-May-28 with convenience method
class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = []
        self.count = 0
#Setters and getters for the model
    def getList(self):
        return self.myList
    def addToList(self,item):
        myItem = item
        myList = self.myList
        myList.append(myItem)
        self.myList=myList
    def getItemAtIndex(self,index):
        return myList[index]
#other methods
    def addRecordToList(self,penguin,action):
        self.addToList([penguin,action])

We included a method addRecordToList() to take the penguin data and make it a list that then ends up in the model’s list. We are trying not to handle data manipulations in the controller, so making methods like this keeps all of those manipulations in the model.  The rest of the code is pretty simple, and is very similar to the example code from the MVC template post. We make some getters and setters for handling lists. There are many more we could create, but we will make what we need as we build the application.

Add the Delegate

In the View, we had the command attribute to signal that a change occurred the controller needs to handle. In the model, there is no such notification, we use a delegate instead. Add this code above the setters and getters in the model:

#(2)Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.modelDidChangeDelegate()

This refers  to a method in the view controller that will handle the change. Now add this as the last line to the addList() method:

self.modelDidChange()

We now will jump to the delegate when there is an entry added. Note the one problem with delegates: We need to remember to put them in the model’s methods where there is change to the model.

Add a Model Instance to the Controller

We have set up our model. Now to use it.  Add this to the view controller, just under the instantiation for the view:

#added instantiation of view 2014 May 26
        self.view = MyView(self)
#(3)added instantiation of model 2014 May 28
        self.model = MyModel(self)

There is now a connection between the controller and the model.

Add the Delegate in the Controller

We have a delegate that needs attention. The model will call a delegate in the controller and we need to do something in that delegate to make sure it is working. Our next post will cover what to do with changing data in a GUI, but for now we’ll just print to the console.  Add this to the code for the controller, under the getters and setters.

#(4) added a delegate for the model 2014 May 28
    def modelDidChangeDelegate(self):
        print("Gork!!")
        print(self.model.getList())

Use the Model

We now have a model, so add the data collected to the list. In the controller, change the  addPressed() method to the following:

    def addPressed(self):
#Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
#(5)added true functionality -- this adds the entry to the model. 2014 May 28
        self.model.addRecordToList(self.view.getPenguinType(),self.view.getPenguinAction())

See If It works

Build and Run. Again there is no change when using the user interface.

Screenshot 2014-05-19 08.53.39
Press the Add button. The console does make a change.

>> ================================ RESTART ================================
>>>
Gork!!
[['Flippy Slippy', 'Sad']]

Add a few more, and the list increases:

>> ================================ RESTART ================================
>>>
Gork!!
[['Flippy Slippy', 'Sad']]
Gork!!
[['Flippy Slippy', 'Sad'], ['Adele', 'Singing']]
Gork!!
[['Flippy Slippy', 'Sad'], ['Adele', 'Singing'], ['Oswald Cobblepot', 'Plotting Evil']]
Gork!!
[['Flippy Slippy', 'Sad'], ['Adele', 'Singing'], ['Oswald Cobblepot', 'Plotting Evil'], ['Tux', 'Happy']]

This is something we should see in the window with a scrolling list. Next time we’ll add a scrolling list to the application.

The Whole Code

Here’s what our code looks like after this lesson. I have also posted code on github for the Popper’s Penguins and MVC  if you want to download it here.

# poppers_penguins_MVC_02
#MVC Version 2014 May 28
#Change: Implements the Model 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;      

#added instantiation of view 2014 May 26
        self.view = MyView(self)
#(3)added instantiation of model 2014 May 28
        self.model = MyModel(self)

    #Handlers -- target action
    def addPressed(self):
#Change getters and setters for the view
        self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added')
#(5)added true functionality -- this adds the entry to the model. 2014 May 28
        self.model.addRecordToList(self.view.getPenguinType(),self.view.getPenguinAction())
    def quitPressed(self):
#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()
#(4) added a delegate for the model 2014 May 28
    def modelDidChangeDelegate(self):
        print("Gork!!")
        print(self.model.getList())

class MyView(Frame):
#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')
#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

#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
#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
    def loadView(self):
        #label
        status_label = ttk.Label(self.frame, textvariable = self.labelText)
        #status_label.configure(font=('Sans','16','bold'),background = 'blue', foreground = '#eeeeff')
        status_label.grid(row=0,column=0,columnspan=4,sticky=EW)

        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)

        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)

#(1)adding the model 2014-May-28 with convenience method
class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = []
        self.count = 0
#(2)Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.modelDidChangeDelegate()
#Setters and getters for the model
    def getList(self):
        return self.myList
    def addToList(self,item):
        myItem = item
        myList = self.myList
        myList.append(myItem)
        self.myList=myList
        self.modelDidChange()
    def getItemAtIndex(self,index):
        return myList[index]
#other methods
    def addRecordToList(self,penguin,action):
        self.addToList([penguin,action])

def main():
    root = Tk()
#(8) Set up the main loop 2014 May 26
    frame = Frame(root, background="#5555ff", takefocus = 0)
    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.