Squeak
  links to this page:    
View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide
Responsibilities of Model, View and Controller
Last updated at 5:06 am UTC on 10 November 2005
by Stefan Matthias Aust

Inside Smalltalk II, by Lalonde and Pugh, is an old but still very interesting book that describes the MVC classes of Smalltalk-80 version 2.x (the predecessor of ObjectWorks and VisualWorks) which was very similar to Squeak.

You may look at the development tools which are implemented with the help of the MVC framework. For example, look at "Inspector open"

Here's some ad hoc information about the MVC framework. Don't expect too much but this might be helpful for you. I'll try to explain the basics here.

Each GUI object (widget or window) consists of a MVC triad.

The model ("M") implements the data part of the object, for example the text shown in text pane of an inspector. The view ("V") defines how the model is shown. Both model and view are connected. The view is a dependent of the model and also directly knows the model. Every time the model changes its values, the view will notice this and can update the display.

The controller ("C") is closely related to the view and controls the behavior of the object. Actually, the controller handles user interaction.

Controller

There are two kinds of controllers:
The global variable "ScheduledControllers" contains a ControlManager instance which maintains a list of all displayed windows and knows which window is currently active.

The ControlManager looks for a window that should become active by sending the message #isControlWanted to each window controller. If the controller answers true, the ControlManager sends #startUp to that controller.

Other widgets work quite similar. They also react to #isControlWanted and #startUp, but they're dispatched by other controllers. We'll see how this happens in a minute.

The abstract class Controller defines the basic behavior of all controllers. Each controller knows its view and its model. It also knows an InputSensor which can be queried about mouse and keyboard state.

These methods implement the basic control loop:

startUp
Called if #isControlWanted answered true. Will initialize, run and terminate the control loop. You'll probably never have to overwrite this method. Any object that responds to #isControlWanted and #startUp is considered as a controller. A scheduled controller must also respond to #closeAndUnschedule.

controlInitialize
Called right after startup, before entering the controlLoop (see below). Does nothing in Controller, but may be overwritten in subclasses. For example, if you want to change the mouse cursor to an I-beam if the mouse enters your widget, then this is the right place.

controlTerminate
Called after the controlLoop (see below) has been terminated. This is an empty method in Controller, but my be overwritten in subclasses. To continue to above example, this would be the place to reset the cursor to its old form.

controlLoop
As long as the controller wants to keep control (#isControlActive answers true) this method loops and calls #controlActivity. You'll probably never have to overwrite this loop. Subclasses will however customize the other two methods.

isControlWanted
Will answer true, if the controller wants control. By default, the controller will be started up when the mouse cursor moves over the display box of the controller's view without pressing any buttons.

isControlActive
Must answer true as long as the controller wants to keep control. This can be the same condition as in #isControlWanted or something very different. Let's think about push button which can be canceled by moving the mouse away from the button while pressing the mouse.(This is the default behavior of known GUI's, only Squeak doesn't have this feature.) The button will get control if the mouse is pressed over the widget, but it retains control as long as the button is pressed, regardless of the mouse position.

controlActivity
This methods implements what will actually happen. It will be overwritten in most subclasses of Controller. The default is to pass the control to some child widget (#controlToNextLevel). The MouseMenuController, for example, determines which mouse button was pressed (red=left, yellow=middle, blue=right) and branches to a special redButtonActivity, yellowButtonActivity or blueButtonActivity method, which then can open a popup menu.


View

Let's look at the View class now. A view knows its controller and model and is also a dependent of that model. The abstract class View is overloaded with methods in Squeak. At least, a view has to implement the methods #display, #update:, #containsPoint: and #subViewWantingControl. The View also maintains a hierarchy of views, knowing a top view, sub views and super views.

For no better reason, the view as chosen as the one that's responsible to construct the MVC triad. It will create and assign a controller and will add and remove the dependency to its model. Overwrite the method #defaultControllerClass to answer the associated Controller subclass and then assign a model using the message #model:.

It's a good idea to assign a display box to the view. Use #window: with a rectangle object as argument. By default, the rectangle is relative to the super view, that is, if you resize the top view, all sub views are scaled as needed. It's however quite complicated code I confess I didn't really understand. You can query the view for its display box using #displayBox (including borders) and #insetDisplayBox (without borders). To assign a border, use #borderWidth: or borderWidthLeft:right:top:bottom: if you want to have different border sizes.

New widgets are added using addSubView: and friends. You can specify the alignment of views, creating relative positioned views without worrying about window rectangles.

The most important View methods in detail:

display
Draw something that represents the view. It is always drawn on the DisplayScreen instance which is assign to the global variable Display. (This doesn't allow view display on more than one source, for example in host windows, unfortunately. A better solution would be to have a method "displayOn: aDisplayScreen.")

The default implementation first draws a border (#displayBorder), then draws the view itself(#displayView) and finally draws the sub views (#displaySubViews).

There are also some methods to #emphasize and #deEmphasize views which can be used to show the active widget or window. However, these methods tend to cause display flickering because they draw views too often.

update: aSymbol
This method is called when the dependent model calls its #changed method. You can use the argument to specify the aspect that has changed, for example to optimize redraw. Most often, however, the view is redrawn, based on the new state of its model. (There's also an implementation of #update in View, which seems to be dubious.)

containsPoint: aPoint
Answer true, if aPoint belongs to the view. You may want to overwrite this method for non-rectangular views. The StandardSystemView, for example, overwrites this method because it once had a non-rectangluar shape because of its label.

There are some dubious and some obviously wrong methods in class View.

Model

Model is the abstract class for all models. You don't however must use subclasses of model, actually the just optimize dependency. Some other methods like #topView or #arrowKey:from: are dubious IMHO.

A model need to implement just one method: #changed:

changed: aSymbol
Inform all dependencies about a change of the receiver. The argument can be used to specify the aspect that has been changed.

addDependent: anObject
Add anObject as a dependent of the receiver. In the case of MVC, this method is send by the View. The view also removes the dependency if needed.

removeDependent: anObject
AnObject isn't a dependent of the receiver anymore.

okToChange
Send by a controller (the comment says, actually it nearly always the model itself; don't trust comments) to ask whether the model is ready to change its state. For example if you change a method in a class browser and you haven't accepted the text pane yet, there's a notifier triggered by this method.

canDiscardEdits
hasUnacceptedEdits
Two other methods sent to views, models or controllers who know how to implement confirmations and notifications. I wish there'd be strict protocol types the compiler could check. This would definitely help in documenting interfaces!

Fix Documentation