Previous
Contents
Next

Using Action Buttons


So far we have used pluggable list views, pluggable text views and some individually programmed paint views. In this section we will take a first look on pluggable button views. We will learn how to use action buttons to change the state of the application.

The objective of this section is to program this application:

A view with two action buttons and two value display views

On the left hand side of this view we have two action buttons, one labelled "+", the other labelled "-". Next to these action buttons we have two value display views, one showing a temperature in centigrades, the other showing the same temperature in fahrenheit.

The user can press the green action button "+" to increment the temperature by one centigrade. To decrement the temperature by 1 °C, the user presses the red action button.

You find the code for this example in the change set ButtonExample1.cs.

Once you have filed in this change set, you can evaluate

ButtonExample1 open

to see the example.


The example uses two new classes:

Additionally, it uses the following existing classes:

Class ButtonExample1 is subclassed to Model and defines a kind of a model with two additional instance variables:

Model subclass: #ButtonExample1
	instanceVariableNames: 'celsius fahrenheit'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MVC-Tutorial'

Use of Instance Variables:

An instance initializer is needed to assign suitable initial values to the instance variables:

initialize

  celsius := 0.0.
  fahrenheit := 32.0.

This is a well-known method to specify the initial size of the top window:

initialExtent
  ^280 @ 80

Two accessors are provided for use by the value display views:

temperatureInCelsius

  ^celsius.
temperatureInFahrenheit

  ^fahrenheit.

The following two methods are provided for use by the buttons.

The decrement button send this method to trigger the reduction of the temperature by one centigrade:

decrement

  celsius := celsius - 1.
  fahrenheit := 1.8*celsius + 32. 
  self changed.

The increment button send this method to increment the temperature by one centigrade:

increment

  celsius := celsius + 1.
  fahrenheit := 1.8*celsius + 32. 
  self changed.

All the interesting stuff is placed in class method open. Here we configure two action buttons and four views. Finally, we put the four views in a top view, which, in this case, is a ColoredSystemView. A ColoredSystemView allows the use of colored action buttons.

To keep this method short, the configuration of an action button is implemented as a separate method.

open
   " ButtonExample1 open"
  
   | topView model textStyle
     incrSwitchView decrSwitchView
     celsiusView fahrenheitView |

   model := self new.
   topView := ColorSystemView new
                 model: model;
                 borderWidth: 1;
                 label: 'Temperature';
                 minimumSize: 200 @ 60;
                 maximumSize: 260 @ 220.

   textStyle := (TextStyle default) copy.

   incrSwitchView := self buildActionButton: Color lightGreen 
                          label: '+'
                          textStyle: textStyle.
   decrSwitchView := self buildActionButton: Color lightRed
                          label: '-'
                          textStyle: textStyle.
   incrSwitchView model onAction: [model increment].
   decrSwitchView model onAction: [model decrement].

   celsiusView := PluggablePassiveValueView
                     on: model
                     valueAccessor: #temperatureInCelsius
                     unit: ' °C'.
   celsiusView insideColor: Color white;
               borderWidth: 1;
               window: (0 @ 0 extent: 60@100).

   fahrenheitView := PluggablePassiveValueView
                        on: model
                        valueAccessor: #temperatureInFahrenheit
                        unit: ' °F'.
   fahrenheitView insideColor: Color white;
                  borderWidth: 1;
                  window: (0 @ 0 extent: 60@100).

   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:

It should be noted that the method buildActionButton:label:textStyle: only creates a PluggableButtonView and its model, an instance of Button. The auxiliary method does not completely configure the Button instance. To complete the configuration of the Button instances, we need the statements

   incrSwitchView model onAction: [model increment].
   decrSwitchView model onAction: [model decrement].

Each of these statements takes one view, accesses its model - a Button - and gives it a parameterless block as an on-action. That block is executed each time the button state changes to #turnOn. In our example the action blocks are kept as simple as possible: An action block sends a single message to model, an instance of BlockExample1.

To create and to configure an action button, we use this method:

buildActionButton: backColor label: aString textStyle: textStyle
  
   | button switchView dt |

   button := Button newOff.
   switchView := PluggableButtonView on: button
			  getState: #isOn
			  action: #turnOn.
   dt := (aString asText addAttribute: TextFontChange font4)
             allBold
             asDisplayText.
   dt foregroundColor: Color black
      backgroundColor: backColor.
   dt textStyle: textStyle.

   switchView label: dt;
              borderWidth: 1;
              insideColor: backColor;
              window: (0 @ 0 extent: 40 @ 50).

   ^switchView.

The first and the second assignment should be explained:


The Value Display View

To display a value, we use a very simple view that implements one single feature: It displays a string that is composed from a temperature value and a unit name. The string is centered to obtain a pleasing resize behaviour.

Class PluggablePassiveValueView is subclassed to View and defines a kind of a view with two additional instance variables:

View subclass: #PluggablePassiveValueView
	instanceVariableNames: 'unitName valueAccessor'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MVC-Tutorial'

Use of Instance Variables:

The protocol of this view is very simple: We override these three instance methods:

Additionally, we define a class method to create an instance and a fourth instance method to initialize an instance. That's all.

To create a completely initialized instance of a PluggablePassiveValueView, we provide a class method that is called with all necessary parameters.

on: aModel valueAccessor: aSymbol unit: aString

  ^self new on: aModel valueAccessor: aSymbol unit: aString

The class method creates the instance and initializes it with the following instance method:

on: aModel valueAccessor: aSymbol unit: aString

  self model: aModel.
  valueAccessor := aSymbol.
  unitName := aString.

Note that we have to write

self model: aModel.

to make the view an observer of its model. It is not sufficient to simply write:

model := aModel.

For a passive view, we do not need a sophisticated controller. An instance of NoController is fully sufficient.

defaultControllerClass
  
   ^NoController.

The instance method displayView does all of the hard work.

displayView
     | box string displayText boundingBoxOfText displayRect |
   self model isNil
      ifTrue: [^self].

   string := (self model perform: valueAccessor)
                  printShowingDecimalPlaces: 1.

   displayText := DisplayText
                    text: (Text string: string, unitName
                                attribute: TextEmphasis normal)
                    textStyle: (TextStyle default copy defaultFontIndex: 5).
  
   displayText foregroundColor: Color black
               backgroundColor: self backgroundColor.

   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.

Here we see some things that we have used earlier:

Note that the method verifies the availability of a model before the model is sent the value access message. Error robust code should do that.

The update method is very simple: The view redisplays itself each time it receives an update request.

update: aParameter
  
  self display.

Upon closer examination of this example, we find that ButtonExample1 sends only one changed message to update two value display views. This is possible because both views share the same model and because each view uses its own accessor method to fetch its display value.


Previous
Contents
Next