Class Notes: Event-Based Animations in Tkinter
Part 1: Getting Started with MVC


Note:

Make sure that you have gone through the notes to install the required graphics modules and have downloaded the cmu_112_graphics.py to same folder as the files you are running.

  1. 112 Graphics Event Loop
  2. Our First Example: A KeyPress Counter
  3. Model-View-Controller (MVC)
  4. Legal event.key values
  5. Moving a Dot with Key Presses
  6. Moving a Dot with Mouse Presses
  7. Moving a Dot with a Timer
  8. Pausing with a Timer
  9. MVC Violations


  1. 112 Graphics Event Loop
  2. 112 graphics event loop diagram

  3. Our First Example: A KeyPress Counter
  4. from cmu_112_graphics import * def appStarted(app): app.counter = 0 def keyPressed(app, event): app.counter += 1 def redrawAll(app, canvas): canvas.create_text(app.width/2, app.height/2, text=f'{app.counter} keypresses', font='Arial 30 bold', fill='black') runApp(width=400, height=400)

  5. Model-View-Controller (MVC)

  6. Note:
    1. We will write animations using the Model-View-Controller (MVC) paradigm.
    2. The model contains all the data we need for the animation. We can store the model in the app object's attributes.
      • In the example above, app.counter is our model.
    3. The view draws the app using the values in the model.
      • In the example above, redrawAll is our view.
    4. The controller responds to keyboard, mouse, timer and other events and updates the model.
      • In the example above, keyPressed is our controller.
    And...
    1. You never call the view or the controllers. The animation framework calls these for you.
      • In the example above, we never call redrawAll or keyPressed. They are called for us.
    2. Controllers can only update the model, they cannot update the view.
      • In the example above, keyPressed cannot call redrawAll.
    3. The view can never update the model.
      • In the example above, redrawAll cannot change app.counter or any other values in the model.
    4. If you violate these rules, it is called an MVC Violation. If that happens, your code will stop running and will display the runtime error for you.

  7. Legal event.key values
  8. # Note: Tkinter uses event.keysym for some keys, and event.char # for others, and it can be confusing how to use these properly. # Instead, cmu_112_graphics replaces both of these with event.key, # which simply works as expected in all cases. from cmu_112_graphics import * def appStarted(app): app.message = 'Press any key' def keyPressed(app, event): app.message = f"event.key == '{event.key}'" def redrawAll(app, canvas): canvas.create_text(app.width/2, 40, text=app.message, font='Arial 30 bold', fill='black') keyNamesText = '''Here are the legal event.key names: * Keyboard key labels (letters, digits, punctuation) * Arrow directions ('Up', 'Down', 'Left', 'Right') * Whitespace ('Space', 'Enter', 'Tab', 'BackSpace') * Other commands ('Delete', 'Escape')''' y = 80 for line in keyNamesText.splitlines(): canvas.create_text(app.width/2, y, text=line.strip(), font='Arial 20', fill='black') y += 30 runApp(width=600, height=400)

  9. Moving a Dot with Key Presses
  10. Moving a Dot with Mouse Presses
  11. from cmu_112_graphics import * def appStarted(app): app.cx = app.width/2 app.cy = app.height/2 app.r = 40 def mousePressed(app, event): app.cx = event.x app.cy = event.y def redrawAll(app, canvas): canvas.create_text(app.width/2, 20, text='Move dot with mouse presses', fill='black') canvas.create_oval(app.cx-app.r, app.cy-app.r, app.cx+app.r, app.cy+app.r, fill='darkGreen') runApp(width=400, height=400)

  12. Moving a Dot with a Timer
  13. from cmu_112_graphics import * def appStarted(app): app.cx = app.width/2 app.cy = app.height/2 app.r = 40 def timerFired(app): app.cx -= 10 if (app.cx + app.r <= 0): app.cx = app.width + app.r def redrawAll(app, canvas): canvas.create_text(app.width/2, 20, text='Watch the dot move!', fill='black') canvas.create_oval(app.cx-app.r, app.cy-app.r, app.cx+app.r, app.cy+app.r, fill='darkGreen') runApp(width=400, height=400)

  14. Pausing with a Timer

  15. Pausing and stepping are super helpful when debugging animations!
    from cmu_112_graphics import * def appStarted(app): app.cx = app.width/2 app.cy = app.height/2 app.r = 40 app.paused = False def timerFired(app): if (not app.paused): doStep(app) def doStep(app): app.cx -= 10 if (app.cx + app.r <= 0): app.cx = app.width + app.r def keyPressed(app, event): if (event.key == 'p'): app.paused = not app.paused elif (event.key == 's') and app.paused: doStep(app) def redrawAll(app, canvas): canvas.create_text(app.width/2, 20, text='Watch the dot move!', fill='black') canvas.create_text(app.width/2, 40, text='Press p to pause or unpause', fill='black') canvas.create_text(app.width/2, 60, text='Press s to step while paused', fill='black') canvas.create_oval(app.cx-app.r, app.cy-app.r, app.cx+app.r, app.cy+app.r, fill='darkGreen') runApp(width=400, height=400)

  16. MVC Violations

    1. Cannot change the model while drawing the view
      from cmu_112_graphics import * def appStarted(app): app.x = 0 def redrawAll(app, canvas): canvas.create_text(app.width/2, 20, text='This has an MVC Violation!', fill='black') app.x = 10 # This is an MVC Violation! # We cannot change the model from the view (redrawAll) runApp(width=400, height=400)

    2. Once again, but with a mutable value (such as a list)
      # Since this version modifies a mutable value in the model, # the exception does not occur immediately on the line of the change, # but only after redrawAll has entirely finished. from cmu_112_graphics import * def appStarted(app): app.L = [ ] def redrawAll(app, canvas): canvas.create_text(app.width/2, 20, text='This also has an MVC Violation!', fill='black') app.L.append(42) # This is an MVC Violation! # We cannot change the model from the view (redrawAll) runApp(width=400, height=400)