appleberry

From Apple to Raspberry Pi: A MVC Template for Tkinter

appleberryIn this installment I’m going to take a side path from the building of the Popper’s penguins app to clarify something I gave in my last post on Python, Tkinter and Raspberry Pi. In the world of Xcode, both for OS X development and for iOS there is this critical idea called the MVC pattern, or Model View Controller.  Apple describes it this way:

The Model-View-Controller design pattern (MVC) is quite old. Variations of it have been around at least since the early days of Smalltalk. It is a high-level pattern in that it concerns itself with the global architecture of an application and classifies objects according to the general roles they play in an application…Object-oriented programs benefit in several ways by adapting the MVC design pattern for their designs. Many objects in these programs tend to be more reusable and their interfaces tend to be better defined. The programs overall are more adaptable to changing requirements—in other words, they are more easily extensible than programs that are not based on MVC. Moreover, many technologies and architectures in Cocoa—such as bindings, the document architecture, and scriptability—are based on MVC and require that your custom objects play one of the roles defined by MVC.

Apple considers MVC a core competency. However most developers do not need to get too deep into what MVC is, since Xcode does a lot of the work for you. There are three parts of MVC which can be basically described as:

  1. Model: The data you are processing
  2. View: The user interface
  3. Controller: Connects the Model to the View

The big advantage of this pattern is developers do not need to rewrite all of their code when one of the three changes. I had a conversation recently about software validation for medical devices adding user interfaces. For a lot of reasons I won’t go into here, Tkinter would never make it through a software validation process. I might have to buy a validated user interface module or write my own to get a medical device approved for use. If everything that is Tkinter is contained in the view, I only have to change the view, not the entire program.

Moving from Xcode to Python MVC: A Bare Bones Template

IN Xcode, models would be classes which handle data. Views are storyboards or interface builder .xib files. The developer connects up the view and the model in a subclass of UIViewController and handles events happening with the model and the view. Much of this is near automatic in Xcode. In Python, this is not the case. We need to spell out everything. I tried finding one nice template or example on the internet that made sense to me, but had little luck. So, I took what I could find and made up my own:

#MVC_Template_01
#2014 May 23  by Steven Lipton http://makeAppPie.com
#Controller initializing MVC -- simplest version possible.
from tkinter import *

#
# A A Model-View-Controller framework for TKinter.
# Model: Data Structure. Controller can send messages to it, and model can respond to message.
# View : User interface elements. Controller can send messages to it. View can call methods from Controller when an event happens.
# Controller: Ties View and Model together. turns UI responses into changes in data and vice versa.

#
#Controller: Ties View and Model together.
#       --Performs actions based on View events.
#       --Sends messages to Model and View and gets responses
#       --Has Delegates
#       --Controllers may talk to other controllers through delegates

class MyController():
    def __init__(self,parent):
        self.parent = parent
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view

        #initialize properties in view, if any
        pass

        #initalize properties in model, if any
        pass

#event handlers -- add functions called by command attribute in view
    def someHandelerMethod(self):
        pass
#delegates -- add functions called by delegtes in model or view
    def modelDidChangeDelegate(self):
        pass

#View : User interface elements.
#       --Controller can send messages to it.
#       --View can call methods from Controller vc when an event happens.
#       --NEVER communicates with Model.
#       --Has setters and getters to communicate with controller

class MyView(Frame):
    def loadView(self):
        pass
    def __init__(self,vc):
        #make the view
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)

        #set the delegate/callback pointer
        self.vc = vc

        #control variables go here. Make getters and setters for them below
        someControlVariable= StringVar()
        someControlVariable = ('nil')

        #load the widgets
        self.loadView()
    #Getters and setters for the control variables.
    def getSomeControlVariable(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setSomeControlVariable(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)

#Model: Data Structure.
#   --Controller can send messages to it, and model can respond to message.
#   --Uses delegates from vc to send messages to the Controller of internal change
#   --NEVER communicates with View
#   --Has setters and getters to communicate with Controller

class MyModel():
    def __init__(self,vc):
        #set delegate/callback pointer
        self.vc = vc
        #initialize model
        self.myModel = 0

#Delegate goes here. Model would call this on internal change
    def modelDidChange(self):
        self.vc.listChangedDelegate()
#Setters and getters for the model
    def getModel(self):
        return self.myModel
    def setList(self,newData)
        self.MyModel = newData
        self.modelDidChange #delegate called on change
#Any internal processing for the model        

def main():
    root = Tk()
    frame = Frame(root )
    root.title('MVC Skeleton')
    app = MyController(root)
    root.mainloop()  

if __name__ == '__main__':
    main()

This is just a bare bones example, though heavily commented. Run it and it will do nothing. Those who have never used MVC or those not comfortable with it will still not understand what is going on. Therefore let’s make a very simple example application piece by piece to explain what MVC does and how to use the template.

The View: What the User Gets

The view is the user interface. The user pushes buttons, move sliders, enters text and all the other types of things we can do with a user interface. The user also gets feedback on their actions from the user interface. For those following my series, we’ve been exploring Tkinter for a while and should be quite comfortable with it. If you are not you might want to check out my introduction to Tkinter The view is all Tkinter code. If I decide to change to a different UI package or make some of my own in pyGame, I’d do all that in the view.

Let’s take an example of a small app that adds a text entry to a list. Here is what our view would look like:

class MyView(Frame):
    def loadView(self):
        quitButton = Button(self.frame,text = 'Quit', command= self.vc.quitButtonPressed).grid(row = 0,column = 0)
        addButton = Button(self.frame,text = "Add", command = self.vc.addButtonPressed).grid(row = 0, column = 1)
        entry = Entry(self.frame,textvariable = self.entry_text).grid(row = 1, column = 0, columnspan = 3, sticky = EW)
        label = Label(self.frame,textvariable = self.vc.label_text).grid(row = 2, column = 0, columnspan = 3, sticky = EW)
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)
        self.vc = vc
        self.entry_text = StringVar()
        self.entry_text.set('nil')
        self.label_text = StringVar()
        self.label_text.set('nil')
        self.loadView()
    def getEntry_text(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setEntry_text(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)
    def getLabel_text(self):
    #returns a string of the Label text
        return self.label_text.get()
    def setLabel_text(self,text):
    #sets the label text given a string
        self.label_text.set(text)

The __init__ function in lines 7 though 13 executes first. It initializes several control variables and sets up the frame. The method then calls loadView() which loads out widgets into the frame in lines 2 through 6. All of that is basic Tkinter code.
What isn’t basic Tkinter is line 10. The constructor method gets a parameter vc, and line 10 assigns it to self.vc. This is a link back to the view controller. A view keeps everything to itself — it encapsulates its data. Neither the model nor the controller can see or change it. A view is not allowed to see or change anything in the model or controller as well. The controller will contain the methods found in our command attributes for our buttons. If we cannot talk to one another, how do we tell the controller to do anything? That is what self.vc is for. It is a pointer used only for callbacks to the controller. That is why we have command = self.vc.addButtonPressed in the add button code. It executes a method in the controller.

Lines 16 through 27 create setters and getters. There are values we want to change or use in the view. This will be a message the view controller will send to the view, and the view can responds by doing something, or sending something back. Remember a setter and a getter is a function or message. It is an indirect change of a property. MVC only forbids direct changes. It does not break the MVC model.

The Model:Where the Data Lives

The model and view are similar in may ways. Both have setters and getters to let the controller use information in each class. The difference between the two is how models will react to change compared to views:

class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = ['duck','duck','goose']
        self.count = 0
#Delegates-- Model would call this on internal change
    def listChanged(self):
        self.vc.listChangedDelegate()
#setters and getters
    def getList(self):
        return self.myList
    def initListWithList(self, aList):
        self.myList = aList
    def addToList(self,item):
        print("returned")
        myList = self.myList
        myList.append(item)
        self.myList=myList
        self.listChanged()

The code for the model here is quite simple. In the real world we might hookup a database or data structure to the model but for our purposes a simple list will do. In structure, we do almost the same as the view — it is a set of setters and getters. There are different types of processing as well, depending on the model. We might add a method for deletion of a list element . The key to the model is that it needs to notify the controller of a change to the model. In our case, it needs to tell the controller we have added an element to the list. While there are ways to do that more automatically, for our simpler model we will use a delegate. Delegates are another indirect method for getting the controller to react when something changes in the Model(or the View in some cases). Line 7-8 is the delegate method on this side. It calls a method in the controller which does the real work.

The Controller: The Hardworking Middle Class

The third part of MVC is the controller, which sits between the view and the model and coordinates between both of them. A lot of the work of the user interface after the user changing something the controller does.

class MyController():
    def __init__(self,parent):
        self.parent = parent
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view
        #initialize objects in view
        self.view.setEntry_text('Add to Label')
        self.view.setLabel_text('Ready')
 #event handlers
    def quitButtonPressed(self):
        self.parent.destroy()
    def addButtonPressed(self):
        self.view.setLabel_text(self.view.entry_text.get())
        self.model.addToList(self.view.entry_text.get())
    def listChangedDelegate(self):
        #model internally chages and needs to signal a change
        print(self.model.getList())

The controller will take information from both the model and the view and process it accordingly. To start, in lines 4 and 5 we set up the model and the view, and give the pointer back to the view controller as a parameter. We then send messages to the view to set our text in the Entry widget and Label widgets. Lines 10-14 are methods for handling view events. the first for clicking the quit button, and the second for clicking the add button. In the case of the add button, we handle this by sending messages to both the model and the view. In lines 15 – 17, we have the delegate from the model. When there is a change to the model, we use this method to do something about it. In this case, we print our contents out on the console.

This is the basic MVC pattern we will follow. The way I set it up this template would be easy to break, by directly assigning something in the model to the view. This is particularly true in Python where variables are bit more fluid than Objective-C. Programming patterns are not a restriction built into the design, though they can be. Programming patterns like MVC are disciplines of the developers to keep to the pattern. When using MVC with this template, the developer needs discipline to follow the rules set out for MVC.

We had a simple example in this code. In our next installment we will apply MVC to Popper’s Penguin app.

The Whole Code

above I gave the template. Here is the whole example code with comments to see it all in context.

#MVC_Template_01
#2014 May 23  by Steven Lipton http://makeAppPie.com
#Controller initializing MVC -- simplest version possible.
from tkinter import *

#
# A A Model-View-Controller framework for TKinter.
# Model: Data Structure. Controller can send messages to it, and model can respond to message.
# View : User interface elements. Controller can send messages to it. View can call methods from Controller when an event happens.
# Controller: Ties View and Model together. turns UI responses into chages in data.

#
#Controller: Ties View and Model together.
#       --Performs actions based on View events.
#       --Sends messages to Model and View and gets responses
#       --Has Delegates 

class MyController():
    def __init__(self,parent):
        self.parent = parent
        self.model = MyModel(self)    # initializes the model
        self.view = MyView(self)  #initializes the view
        #initialize objects in view
        self.view.setEntry_text('Add to Label') #a non cheat way to do MVC wiht tkinter control variables
        self.view.setLabel_text('Ready')
 #event handlers
    def quitButtonPressed(self):
        self.parent.destroy()
    def addButtonPressed(self):
        self.view.setLabel_text(self.view.entry_text.get())
        self.model.addToList(self.view.entry_text.get())
    def listChangedDelegate(self):
        #model internally chages and needs to signal a change
        print(self.model.getList())

#View : User interface elements.
#       --Controller can send messages to it.
#       --View can call methods from Controller vc when an event happens.
#       --NEVER communicates with Model.
#       --Has setters and getters to communicate with controller

class MyView(Frame):
    def loadView(self):
        quitButton = Button(self.frame,text = 'Quit', command= self.vc.quitButtonPressed).grid(row = 0,column = 0)
        addButton = Button(self.frame,text = "Add", command = self.vc.addButtonPressed).grid(row = 0, column = 1)
        entry = Entry(self.frame,textvariable = self.entry_text).grid(row = 1, column = 0, columnspan = 3, sticky = EW)
        label = Label(self.frame,textvariable = self.label_text).grid(row = 2, column = 0, columnspan = 3, sticky = EW)
    def __init__(self,vc):
        self.frame=Frame()
        self.frame.grid(row = 0,column=0)
        self.vc = vc
        self.entry_text = StringVar()
        self.entry_text.set('nil')
        self.label_text = StringVar()
        self.label_text.set('nil')
        self.loadView()
    def getEntry_text(self):
    #returns a string of the entry text
        return self.entry_text.get()
    def setEntry_text(self,text):
    #sets the entry text given a string
        self.entry_text.set(text)
    def getLabel_text(self):
    #returns a string of the Label text
        return self.label_text.get()
    def setLabel_text(self,text):
    #sets the label text given a string
        self.label_text.set(text)

#Model: Data Structure.
#   --Controller can send messages to it, and model can respond to message.
#   --Uses delegates from vc to send messages to the Controll of internal change
#   --NEVER communicates with View
#   --Has setters and getters to communicate with Controller

class MyModel():
    def __init__(self,vc):
        self.vc = vc
        self.myList = ['duck','duck','goose']
        self.count = 0
#Delegates-- Model would call this on internal change
    def listChanged(self):
        self.vc.listChangedDelegate()
#setters and getters
    def getList(self):
        return self.myList
    def initListWithList(self, aList):
        self.myList = aList
    def addToList(self,item):
        print("returned")
        myList = self.myList
        myList.append(item)
        self.myList=myList
        self.listChanged()

def main():
    root = Tk()
    frame = Frame(root,bg='#0555ff' )
    root.title('Hello Penguins')
    app = MyController(root)
    root.mainloop()  

if __name__ == '__main__':
    main()  

7 thoughts on “From Apple to Raspberry Pi: A MVC Template for Tkinter”

    1. More than a typo. But yes a mistake. That sentence and the one after it should read:
      “The key to the model is that it needs to notify the controller of a change to the model. In our case, it needs to tell the controller we have added an element to the list. While there are ways to do that more automatically, for our simpler model we will use a delegate. ”

      corrected. Thank you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s