You find the code for this example in the change set ValueHolderExample.cs.
This example demonstrates the use of two ValueHolders. One ValueHolder displays a temperature in centigrades, the other ValueHolder displays the same temperature in Fahrenheit. Two buttons are provided to increment and to decrement the centigrade temperature value.
Evaluate
TemperatureCalculator open
to open the view.
To display the contents of a value holder, we need a view. For this example, the view is very simple: In principle, we need three instance methods:
For a better visual result, we add an instance variable that is used to store the unit name, and an instance method that assigns a value to that variable.
The class TemperatureView is a direct subclass of View:
View subclass: #TemperatureView instanceVariableNames: 'unitName' classVariableNames: '' poolDictionaries: '' category: 'MVC-Tutorial'
This view will use an instance of ValueHolder as its model. That model class does not implement support for input processing. It is therefore a good choice to create instances of TemperatureView with a NoController:
defaultControllerClass " answer the class of a typically useful controller" ^NoController.
The method displayView is a bit technical, but straightforward: It creates a DisplayText with the string to be displayed, and displays it centered in the view.
displayView | box string displayText boundingBoxOfText displayRect | self model isNil ifTrue: [^self]. string := self model contents printShowingDecimalPlaces: 1. displayText := DisplayText text: (Text string: string, unitName attribute: TextEmphasis normal) textStyle: (TextStyle default copy defaultFontIndex: 5). displayText foregroundColor: Color black backgroundColor: Color white. boundingBoxOfText := displayText boundingBox. box := self insetDisplayBox. displayRect := Rectangle center: box center extent: boundingBoxOfText extent. displayText displayOn: Display at: displayRect origin clippingBox: box rule: Form over fillColor: nil.
A ValueHolder sends the message update: #contents to all its dependents when the value was changed. To react to a value change, class TemperatureView has to implement the instance method update:. This method checks the value of the message argument and redisplays the entire view when the message argument is #contents.
update: aParameter aParameter = #contents ifTrue: [self display].
The method that assigns the unit name is very simple:
unitName: aString unitName := aString
The application itself is implemented by a subclass of Model.
Model subclass: #TemperatureCalculator instanceVariableNames: 'celsius fahrenheit' classVariableNames: '' poolDictionaries: '' category: 'MVC-Tutorial'
celsius - ValueHolder
This value holder stores the temperature value in centigrades.
fahrenheit - ValueHolder
This value holder stores the temperature value in fahrenheit.
The instance method initialize creates two initialized ValueHolders. Both value holders are initialized to the same temperature: the freezing point of water.
initialize celsius := ValueHolder new. celsius contents: 0.0. fahrenheit := ValueHolder new. fahrenheit contents: 32.0.
celsius " answer the ValueHolder that is used to store the centigrade temperature " ^celsius
fahrenheit " answer the ValueHolder that is used to store the Fahrenheit temperature " ^fahrenheit
These two accessors are only needed by the class method open, which builds the application window. It is entirely possible to use an instance method to build the application window and when this approach is choosen, these accessors are not needed at all.
initialExtent ^280 @ 80
The messages decrement and increment are sent by the switches to inform the application about a button event. The methods adjust both temperature values.
decrement | v newValue | v := celsius contents. newValue := v - 1. celsius contents: newValue. fahrenheit contents: 1.8*newValue + 32.
increment | v newValue | v := celsius contents. newValue := v + 1. celsius contents: newValue. fahrenheit contents: 1.8*newValue + 32.
The entire view is assembled in class method open. This method creates the top view and four subviews.
open " TemperatureCalculator open " | topView model incrButton decrButton incrSwitchView decrSwitchView celsiusView fahrenheitView dt textStyle | " create the top window " model := self new. topView := ColorSystemView new model: model; label: 'Temperature'. topView minimumSize: 200 @ 60; maximumSize: 260 @ 220. " create the model for the increment button and the button itself " incrButton := Button newOff. incrButton onAction: [model increment]. incrSwitchView := PluggableButtonView on: incrButton getState: #isOn action: #turnOn. textStyle := (TextStyle default) copy. dt := ('+' asText addAttribute: TextFontChange font4) allBold asDisplayText. dt foregroundColor: Color black backgroundColor: Color lightGreen. dt textStyle: textStyle. incrSwitchView label: dt. incrSwitchView borderWidthLeft: 2 right: 1 top: 2 bottom: 1; insideColor: Color lightGreen. incrSwitchView window: (0 @ 0 extent: 40 @ 50). " create the model for the decrement button and the button itself " decrButton := Button newOff. decrButton onAction: [model decrement]. decrSwitchView := PluggableButtonView on: decrButton getState: #isOn action: #turnOn. dt := ('-' asText addAttribute: TextFontChange font4) allBold asDisplayText. dt foregroundColor: Color black backgroundColor: Color lightRed. dt textStyle: textStyle. decrSwitchView label: dt. decrSwitchView borderWidthLeft: 2 right: 1 top: 1 bottom: 2; insideColor: Color lightRed. decrSwitchView window: (0 @ 0 extent: 40 @ 50). " create the view for the value holder 'celsius' " celsiusView := TemperatureView new. celsiusView model: model celsius; unitName: ' °C'. celsiusView insideColor: Color white; borderWidthLeft: 1 right: 1 top: 2 bottom: 2. celsiusView window: (0 @ 0 extent: 60@100). " create the view for the value holder fahrenheit " fahrenheitView := TemperatureView new. fahrenheitView model: model fahrenheit; unitName: ' °F'. fahrenheitView insideColor: Color white; borderWidthLeft: 1 right: 2 top: 2 bottom: 2. fahrenheitView window: (0 @ 0 extent: 60@100). " install all views as subviews of the top view " topView addSubView: incrSwitchView. topView addSubView: decrSwitchView below: incrSwitchView. topView addSubView: celsiusView toRightOf: incrSwitchView. topView addSubView: fahrenheitView toRightOf: celsiusView. topView controller open
Some details of this method are worth being commented:
Upon closer examination of this example, we find that TemperatureCalculator itself does almost nothing to support its views: It provides an initial extent for the top view, that's all. All subviews have their own models. TemperatureCalculator references two of these submodels: The value holders. To display a value, the TemperatureCalculator communicates it to the value holders, which have the responsibility to support their views. Value holders are very simple components that can be used to structure an application model.