Post on 20-Jan-2016
Computer Science 111
Fundamentals of Programming IModel/View/Controller and
Data model design
I/O and Computations
Add game logic to enforce rules and detect a winner
Add arithmetic to perform calculations
The Model/View Pattern
Data Model View (User Interface)
The data model consists of software components that manage a system’s data (game logic or calculations)
The view consists of software components that allow human users to view and interact with the data model
The view can be a TUI or a GUI, on the same data model
The View
TicTacToeApp
TTTSquare
Button
9
EasyFrame
EasyCanvas
The View and the Data Model
TicTacToe
list
String
String
9
TicTacToeApp
TTTSquare
Button
9
displaysEasyFrame
EasyCanvas
Model/View/Controller Pattern
GUI-based, event-driven programs can be further decomposed by gathering code to handle user interactions into a third component called the controller
Controller
ModelView Manages dataDisplaysdata
Responds to user events
The Controller
• In Python, the controller consists of the methods that respond to user events
• These methods are defined in the main view class, and are associated with the command attribute of the buttons
• Other types of widgets can have their own event-handling methods (for mouse presses, etc.)
Event-Driven Programming
• Set up a window with its widgets
• Connect it to a data model
• Wait for users to press buttons, enter text, drag the mouse, etc.
• Respond to these events by running methods that update the data model and the view
Add a Data Model to Tictactoe
• A rough prototype of the model tracks just the current letter (X or O)
• The model’s initial state contains a randomly chosen letter
• The method nextLetter() changes the model’s state to the other letter and returns that letter
model = TicTacToe() # Returns a new model with a randomly chosen letter
str(model) # Returns the string rep of the model
letter = model.nextLetter() # Changes state to the other letter and returns it
model.newGame() # Resets the state to a randomly chosen letter
The Model’s Interface
These are the operations that the view and the controller know about and can use
class TicTacToeApp(EasyFrame): """Displays a tictactoe board in the window."""
def __init__(self): """Sets up the window and the panels.""" EasyFrame.__init__(self, title = "TicTacToe") self.model = TicTacToe() # Create the model color = "black" number = 0 for row in range(3): for column in range(3): square = TTTSquare(parent = self, width = 50, height = 50, background = color, number = number, model = self.model) # Pass it to each square self.addCanvas(square, row = row, column = column) number += 1 if color == "black": color = "white" else: color = "black"
Changes to Main View Class
When the user clicks in a square, that square gets a letter from the model to display in the view
class TTTSquare(EasyCanvas): """Represents a tictactoe square."""
def __init__(self, parent, width, height, background, number, model): """Sets up the tictactoe square.""" EasyCanvas.__init__(self, parent, width = width, height = height, background = background) self.model = model # Save a reference to the model self.number = number self.width = width self.height = height self.letter = "" # No letter in the square yet def mousePressed(self, event): """Displays an X or an O if the square is unoccupied.""" if not self.letter: self.letter = self.model.nextLetter() # Access the model self.drawText(self.letter, self.width // 2, self.height // 2, fill = "red")
Changes to the Canvas Class
When the user clicks in a square, that square gets a letter from the model to display in the view
import random
class TicTacToe(object): """Models a tictactoe game."""
def __init__(self): """Sets up the model.""" self.newGame()
def __str__(self): """Returns the string rep of the model.""" return self.letter def newGame(self): """Resets the game to its initial state.""" self.letter = random.choice(("X", "O"))
def nextLetter(self): """Returns the next letter for play.""" if self.letter == "X": self.letter = "O" else: self.letter = "X" return self.letter
The Model Class
def main(): """Starting point for the application.""" model = TicTacToe() print(model) for count in range(5): print(model.nextLetter())
if __name__ == "__main__": main()
Testing the Model
Since the model is independent of any view, you can (and should) test it thoroughly before hooking a view onto it
Add a Data Model to the Calculator
Calculator
total
CalculatorApp
Button
Label
19
displaysEasyFrame
Add a Data Model to the Calculator
• The model will handle the calculation when the +, -, X, /, or = button is clicked
• The model tracks a running total, as well as a previous operator and operand
• The controller– Obtains the number in the digits label
– Passes the operator and the number to the model
– Resets the digits label to the result
model = Calculator() # Returns a new model with total = 0
str(model) # Returns the model’s state (the current total)
model.applyOperator(operator, # Performs an arithmetic operation and updates the operand) # total
The Model’s Interface
When the operator is =, applyOperator works with a previous operator and possibly a previous operand, if they exist; the model tracks these as well
class CalculatorApp(EasyFrame): """Displays labels in the window's quadrants."""
def __init__(self): """Sets up the window and the labels.""" EasyFrame.__init__(self, background = "black", title = "Calculator", resizable = False) self.calculator = Calculator() # Instantiate the data model self.operatorEntered = False self.digitsLabel = self.addLabel("0", row = 0, column = 0, columnspan = 4, sticky = "E") self.addButton("/", row = 1, column = 3, command = self.makeOperatorCommand("/")) # buttons for 3 other operators, X, +, -, go here
self.addButton("0", row = 5, column = 0, command = self.makeDigitCommand("0")) # Buttons for other digits and operators go here
Changes to Main View Class
makeOperatorCommand builds a command for each operator
class CalculatorApp(EasyFrame): """Displays labels in the window's quadrants."””..
def clear(self): """Restores the model and view to their original states.""" self.calculator = Calculator() self.operatorEntered = False self.digitsLabel["text"] = "0" self.clearButton["text"] = "AC"
Changes to Main View Class
clear creates a new model, resets the view’s reference to it, and updates the view
class CalculatorApp(EasyFrame): """Displays labels in the window's quadrants."””..
# Event handling method builder for digit buttons def makeDigitCommand(self, buttonText): """Define and return the event handler for a button.""" def addDigit(): if self.operatorEntered or self.digitsLabel["text"] == "0": self.digitsLabel["text"] = "" self.operatorEntered = False self.digitsLabel["text"] += buttonText if buttonText != "0": self.clearButton["text"] = "C" return addDigit
The Controller for All Digits
operatorEntered is True after an operator is entered, which allows the user to begin a new series of digits
When it’s False, the user continues the series of digits
class CalculatorApp(EasyFrame): """Displays labels in the window's quadrants."””..
# Event handling method builder for /, X, +, -, and = buttons def makeOperatorCommand(self, buttonText): """Define and return the event handler for a button.""" def applyOperator(): number = self.digitsLabel["text"] if "." in number: number = float(number) else: number = int(number) self.calculator.applyOperator(buttonText, number) self.digitsLabel["text"] = str(self.calculator) self.operatorEntered = True return applyOperator
The Controller for 4 Operators
Get the label’s text, convert it to the appropriate number, pass it and the operator to the model for calculation, and display the model’s state
For Friday
Read Chapter 2 on
Collections