Previous
Contents
Next

Advanced Programming Techniques for MVC

Adding Horizontal Scrolling


The new controller is implemented as a subclass of ScrollController. Class ScrollController implements vertical scrolling and the new class has to add only horizontal scrolling. To this purpose, we add three instance variables to keep the horizontal scrollbar, the horizontal marker and the screen area that is obscured by the horizontal scrollbar when it is displayed.

The change set for this example is FullScrollExample01.1.cs

ScrollController subclass: #BidirectionalScrollController
      instanceVariableNames: 'scrollBarX markerX savedAreaX'
      classVariableNames: 'CursorLeftArrow'
      poolDictionaries: ''
      category: 'MVCTutorial-Scrolling'

Use Of Instance Variables:

Use of Class Variables:

To demonstrate the use of that controller, we introduce a new view:

View subclass: #FormDisplayView
      instanceVariableNames: 'form scrollOffset'
      classVariableNames: ''
      poolDictionaries: ''
      category: 'MVCTutorial-Scrolling'

The example is implemented in class :

Model subclass: #BidirectionalScrollExample1
       instanceVariableNames: ''
       classVariableNames: ''
       poolDictionaries: ''
       category: 'MVCTutorial-Scrolling'

This model will be only used to provide an initial window extent:

intitialExtent
 ^300@320

The methods of BidirectionalScrollController are schematically derived from the methods of the superclass. We have to:

Thus, the method computeMarkerRegion of ScrollController, which reads:

computeMarkerXRegion
   " Answer the rectangular area in which the gray area of the scroll bar
     should be displayed. "
 ^0@0 extent: Preferences scrollBarWidth @
              ((view window height asFloat /
                   view boundingBox height * scrollBar inside height)
                 rounded min: scrollBar inside height)

becomes:

computeMarkerXRegion
   " Answer the rectangular area in which the gray area of the scroll bar
     should be displayed. "

 ^0@0 extent: 
       ((view window width asFloat /
             view boundingBox width * scrollBarX inside width)
            rounded min: scrollBarX inside width
       ) @ Preferences scrollBarWidth

Note that the coordinates of the extent point were transposed.

Most of the important code is in the FormViewDisplay:

This view uses a BidirectionalScrollController as its preferred controller:

defaultControllerClass
  ^BidirectionalScrollController

The size of the inset display box and the current scroll position are answered by the method window:

window
  ^scrollOffset negated extent: self insetDisplayBox extent

The size of the image to be displayed is:

boundingBox
   ^form isNil
       ifTrue: [self getWindow]
       ifFalse: [0@0 extent: form extent].

Displaying:

displayView
form isNil
  ifTrue: [self clearInside]
  ifFalse:
    [Display copy: (scrollOffset negated extent: self insetDisplayBox extent)
             from: form
             to: self insetDisplayBox origin
             rule: Form over
    ]

Scrolling

scrollBy: aPoint
   " the coordinates of aPoint can be floats "

  | oldOffset cornerOfScrolledForm corner |
  oldOffset := scrollOffset.
  scrollOffset := scrollOffset + aPoint rounded.
  cornerOfScrolledForm := scrollOffset + form extent.
  corner := self insetDisplayBox extent.
  cornerOfScrolledForm x < corner x
    ifTrue: [scrollOffset := (corner x - cornerOfScrolledForm x) @ 0 + scrollOffset].
  cornerOfScrolledForm y < corner y
    ifTrue: [scrollOffset :=  0 @ (corner y - cornerOfScrolledForm y) + scrollOffset].
  ^oldOffset ~= scrollOffset
    " a value of true tells the controller that the view should be redisplayed "

It is the responsibility of the controller to redraw the view during scrolling. This is done in ScrollController>>scrollView: and in BidirectionalScrollController>>scrollViewX:, where we find:

(view scrollBy:(<aPoint>))
  ifTrue: [view clearInside; display. ^true]

This is a very general solution. Due to its generality, it can not use information about the view content that would perhaps allow the use of a more efficient redraw algorithm. Some specialized controllers (like ListController and ParagraphEditor) delegate the view repainting to their views.


Previous
Contents
Next