Contents
Next

Advanced Programming Techniques for MVC

Variable Subviews


This section explains how to program a window that replaces its subview with a different subview. On the top of the window we place two buttons to select the subview to be displayed. When the switch Drawing is pressed, the two subviews of a simple graphics demo are shown:

When the switch Text is pressed, the text style demo is shown:

The example reuses code from two previous examples.

The change set for this example is VariableViewDemo.1.cs. This change set is complete and independent from other change sets. You do not need to file in change sets of other examples.

For this example we need two auxiliary classes:

ActivationSwitch is a subclass of Switch that cannot be switched off by its controller.

Switch subclass: #ActivationSwitch
       instanceVariableNames: ''
       classVariableNames: ''
       poolDictionaries: ''
       category: 'MVCTutorial-AdvancedExamples

Instance protocol:

okToChange
 " a change is only allowed when the switch is currently off. "
^on not

The controller will send this message when you set the instance variable askBeforeChanging to true.

ContainerView is a view that has protocol to

The design of the subview follows the pattern of pluggable views: The view keeps a method selector which it uses

View subclass: #ContainerView
     instanceVariableNames: 'subviewSelector'
     classVariableNames: ''
     poolDictionaries: ''
     category: 'MVCTutorial-AdvancedExamples'

Class Protocol:

on: aModel subviewSelector: aSelector window: aRectangle
  ^self new
     window: aRectangle;
     setModel: aModel subviewSelector: aSelector

Instance Protocol:

setModel: aModel subviewSelector: aSelector

  self model: aModel.
  subviewSelector := aSelector.
  aModel perform: aSelector with: self.
update: aParam

  aParam = subviewSelector
    ifTrue:
      [self releaseSubViews.
       model perform: subviewSelector with: self.
       self display;
            emphasize
      ]

The example itself uses the model

Model subclass: #VariableSubViewExample
      instanceVariableNames: 'switch1 switch2 currentAspect'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'MVCTutorial-AdvancedExamples'

Variables switch1 and switch2 keep two instances of ActivationSwitch, the models of the two PluggableSwitchViews. The variable currentAspect keeps a message selector of a message that is sent to provide the subviews for a ContainerView.

Class Protocol:

An auxiliary method is used to create switch labels in a suitable style and size:

labelFor: aString
    " create a DisplayText for the given string.
      Change this method to display the switch labels
      with a different text style or in a different size. "
    | text textStyle |
  text := Text string: aString attribute: TextEmphasis normal.
  textStyle := TextStyle default copy.
  textStyle defaultFontIndex: (textStyle fontIndexOfPointSize: 18).
  ^DisplayText text: text textStyle: textStyle.

This method is used twice in the open method:

open
   "VariableSubViewExample open"
     | topView model switch1 switch2 subviewContainer |
   model := self new.
   topView := ColorSystemView new.
   topView label: 'Variable Subview Demo';
           model: model;
           borderWidth: 1.
   switch1 := PluggableButtonView
                  on: model switch1
                  getState: #isOn
                  action: #switch.
   switch1 label: (self labelFor: 'Drawing');
           borderWidth: 1;
           askBeforeChanging: true;
           window: (0 @ 0 extent: 100 @ 15).
   topView addSubView: switch1.

   switch2 := PluggableButtonView
                  on: model switch2
                  getState: #isOn
                  action: #switch.
   switch2 label: (self labelFor: 'Text');
           borderWidth: 1;
           askBeforeChanging: true;
           window: (0 @ 0 extent: 100 @ 15).
   topView addSubView: switch2
            toRightOf: switch1.

   subviewContainer :=
      ContainerView 
           on: model
           subviewSelector: #subViewAccessor:
           window: (0 @ 0 extent: 200 @ 300).

   topView addSubView: subviewContainer
                below: switch1.

   topView controller open

Instance Protocol:

category: initialize-release

Here we create the models for the two switches and initialize the variable currentAspect. With the initial values that are used here, the window will be opened with the drawing.

initialize

  switch1 := ActivationSwitch newOn
                          onAction: [self button1IsOn].
  switch2 := ActivationSwitch newOff
                          onAction: [self button2IsOn].
  currentAspect := #addGraphicView:

category: window geometry

initialExtent
  ^500 @ 340

category: buttons

When a switch is turned on, it

button1IsOn

  switch2 turnOff.
  currentAspect := #addGraphicView:.
  self changed: #subViewAccessor:
button2IsOn

  switch1 turnOff.
  currentAspect := #addTextView:.
  self changed: #subViewAccessor:

category: accessing

These methods are needed to fetch the models for the two PluggableButtonViews.

switch1
   ^switch1
switch2
   ^switch2

category: subview support

Method subViewAccessor delegates the task to provide subviews for the requestor to one of the methods addGraphicView, addTextView:.

subViewAccessor: aViewContainer
  self perform: currentAspect with: aViewContainer
addGraphicView: subviewContainer

  GraphicalDemo2 addSubviewsFor: subviewContainer.
addTextView: subviewContainer

  TextStyleDemo addSubviewsFor: subviewContainer.

In TextStyleDemo, we have to add this class method:

addSubviewsFor: container

    | model frame textView fontList fontSizesList attributesList |

  frame := container window.
  model := self new.

  textView := PluggableTextView
                    on: model
                    text: #getText
                    accept: nil
                    readSelection: nil
                    menu: #getTextMenu:.
  textView borderWidth: 1.
  textView 
    window: (frame width *4 /20 @ 0 extent: frame width * 16/20 @ frame height).
  container addSubView: textView.<

  fontList := PluggableListView
                      on: model
                      list: #fontFamilyList
                      selected: #currentFontFamily
                      changeSelected: #selectFontFamily:
                      menu: nil
                      keystroke: nil.
  fontList controller: (fontList defaultController
                               terminateDuringSelect: true);
           autoDeselect: false.
  fontList borderWidth: 1.
  fontList
    window: (0 @ 0 extent: (frame width * 4/20) @ (frame height / 3)).
  container addSubView: fontList toLeftOf: textView.

  fontSizesList := PluggableListView
                      on: model
                      list: #fontSizesList
                      selected: #currentFontSize
                      changeSelected: #selectFontSize:
                      menu: nil
                      keystroke: nil.
  fontSizesList autoDeselect: false.
  fontSizesList borderWidth: 1.
  fontSizesList
    window: (0 @ 0 extent: (frame width * 4 / 20) @ (frame height / 3)).
  container addSubView: fontSizesList below: fontList.

  attributesList := PluggableListViewOfMany
                      on: model
                      list: #textAttributesList
                      primarySelection: #listIndex
                      changePrimarySelection: #toggleListIndex:
                      listSelection: #listSelectionAt:
                      changeListSelection: nil
                      menu: nil
                      keystroke: nil. 
  attributesList borderWidth: 1.
  attributesList
    window: (0 @ 0 extent: (frame width * 4 / 20) @ (frame height/3)).
  container addSubView: attributesList below: fontSizesList.

What we have learned:

As far as it is now developed, this example has an annoying deficiency: A subview that is again selected, comes up with some initial settings, not with the settings that it had when is was replaced with another subview. It turns out that is deficiency can be fixed easily. This is what we will discuss in the next section.


Contents
Next