In 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.
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 Reply