Previous
Contents
Next

Advanced Programming Techniques for MVC

Views that Observe Several Data Sources


A view is a dependent of its model; it reacts to changes of its model. A view and a model cooperate to keep a visible presentation of some information up to date. The following distribution of responsibilities governs the cooperation between a view and a model:

In pattern terminology, these rules constitute the observer pattern where the view acts as observer while the view model is the observed subject.

All views that you find in Squeak observe one model to obtain all data to display. It is however possible and sometimes very convenient to use different model components to store various data elements.

This example shows the use of a subclass of PluggableButtonView that uses a second model to obtain its display colors.

You find this example in change set ObserverExample1.1.cs.

Three classes are defined for this example:

The class definitions are:

Model subclass: #VisualButtonProperties
      instanceVariableNames:
          'foreColor backColor borderColor
           disabledForeColor disabledBackColor
           emphasizedLabelColor deemphasiszedLabelColor
           emphasizedSelectionColor deEmphasizedSelectionColor'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'MVCTutorial-Advanced'

All instance variables of this class keep Color values.

PluggableButtonView subclass: #PluggableColoredButtonView
      instanceVariableNames: 'visualProperties'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'MVCTutorial-Advanced'

This definition adds one variable to the set of inherited instance variables. The new variable references an instance of VisualButtonProperties.

Model subclass: #ObserverExample
      instanceVariableNames:
          'buttonColors switches clearAll selectForeColor selectBackColor'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'MVCTutorial-Advanced'

This is a very structured model: The instance variable switches contains an array of Switches and all other instance variables reference various kinds of model subclasses.


Most of the protocol of these three classes is very simple, but some remarks are nevertheless helpful:

In VisualButtonProperties, we have two kinds of setters:

The simple setters are only meant for instance initialization.

The method postCopy is used to support copy, a method that is inherited from Object.

postCopy
   " copy sends this message to a new shallow copy.
     This method removes the dependents from the
     shallow copy. "
  self breakDependents.

In PluggableColoredButtonView we have methods that access the display colors to be used from instance variable visualProperties.

The method visualProperties: registers the view as a dependent of the displayProperties, which are also assigned to the instance variable visualProperties:

visualProperties: displayProperties
      "   Set the receiver's visual properties to 
          displayProperties, add the
	  receiver to displayProperties's list of 
	  dependents. Subsequent changes to displayProperties
          (see Model|change) will result in View|update: 
          messages being sent to the receiver."

 visualProperties ~~ nil & (visualProperties ~~ displayProperties)
   ifTrue: [visualProperties removeDependent: self].
 displayProperties ~~ nil & (displayProperties ~~ visualProperties)
   ifTrue: [displayProperties addDependent: self].
 visualProperties := displayProperties.

Method update: receives change notifications from two models. It handles the notification from its visual properties and delegates all other notifications to the superclass method.

update: argument
 argument == visualProperties
   ifFalse: [super update: argument]
   ifTrue: [self display; emphasizeView].

An instance of a PluggableColoredButtonView can register itself as dependent of two different models. To remove both dependencies when the view dropped, we have to redefine the method release. The new definition disconnects the view from its visual properties and calls the superclass method to disconnect the view from its model.

release
 visualProperties removeDependent: self.
 super release.
 visualProperties := nil.

In ObserverExample, we have:

selectBackColor
    " update the background Color. Note that
      buttonColor is a model component that
      informs its dependents about the change. "
 buttonColors backgroundColor: Color fromUser.

This looks very simple. In fact, this is the point where the carefully choosen model structure pays off. Note that buttonColors references an instance of VisibleButtonProperties that has four dependents. The seemingly simple change of the background color is notified to all four dependents and causes a change of the background color of four PluggableColoredButtonViews. This is what a hunter would call a "four rabbits with one shoot" .


In this example, the second data source, an instance of VisualButtonProperties, describes in fact the visual properties of a view. In fact, the technique is not limited to visual properties, it can be used for all view properties that a model should be able to change (these are called "model-controlled properties") and it is especially useful for shareable properties.

For views that can be enabled or disabled, the state would be a model-controlled and shareable property. A ValueHolder could be used to represent that property.

For views that observe a lot of data sources, the update: method can become quite complicated. What is worse, the evaluation of several conditions will inevitably slow down updating. One should think about ways to keep update methods simple and efficient. One way to achieve that is to replace methods that use a lot of conditions to identify the data source:

update: argument
  argument = <instVar1>
    ifTrue:
      [<action 1>.
       ^self].
  argument = <instVar2>
    ifTrue:
      [<action 2>.
       ^self].
  argument = <instVar3>
    ifTrue:
      [<action 3>.
       ^self].

with something like:

update: argument
 argument manifestYourselfIn: self

This is the double dispatch pattern. It requires that all possible data sources implement the same protocol to support view updates.

There is an even better solution to avoid conditions in update methods: We can provide a separate update protocol for visual properties:

in VisualButtonProperties

changed: aParameter 
  "Receiver changed. The change is denoted by the argument
   aParameter. Usually the argument is a Symbol that is part
   of the dependent's change protocol. Inform all of the
   dependents."

self dependents do:
   [:aDependent |
     aDependent updateVisualProperties: aParameter]

in PluggableColoredButtonView:

updateVisualProperties: aspect

 aspect == visualProperties
   ifTrue: [self display; emphasizeView].

With the definition of a specialized update message, we create a structure that is sometimes called a message channel.

The method update: can be deleted, the method of the superclass is sufficient.

You find this example in change set ObserverExample2.1.cs.

What we have learned:


Previous
Contents
Next