Squeak
  links to this page:    
View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide
Morphic Counter Example
Last updated at 5:03 am UTC on 10 March 2016
updateMe – This is the most complete Morphic tutorial I have found to date. It would be great to update it to 3.9, especially in regard to the use of AlignmentMorph – Matthew Fulmer
Revision history
First Draft: Compilation of Squeak list messages by Hannes Hirzel
Second Draft: Proof reading, testing and additional comments by Daniel McBrearty
Third Draft: Hannes Hirzel
Elimination of the instructions from Tim Olson on how to do it by direct manipulation, because Dan Shafter has done a specific Counter Tutorial for this. This tutorial deals with two other ways of creating a simple counter.
Revision of the rest and test with Squeak release versions 2.7, 2.8 and 3.0.
Tested under 3.1 Carlos Crosetti, removing the orientation: messages.

If you like to help please feel free to either add questions, comments or elaborate on the instructions. Anybody who has the knowledge is invited to revise it and come up with either additions on the approaches described here or add additional ones. This tutorial could serve as a starting point for a wider discussion on how to work with Morphic.


Introduction


An very simple example - let's say a counter - helps to learn Morphic. Using a counter as an example has some tradition in the Smalltalk literature and many books of the last twenty years have an example on this.

There are different approaches to using Morphic. This tutorial elaborates these.

The problem


Andre M. (Mike) Maloney" asked in comp.lang.smalltalk

> Hi all,

I'm just starting to learn Smalltalk and am using Squeak (2.4c). I'm trying to build a simple counter and need some help. The counter will have three buttons and a display. The displayed value will start at one and the buttons will increment the displayed value by one, decrement the displayed value by one, and reset the displayed value to zero.

I've found the ValueHolder, button, and PluggableButtonController classes, but how do I put the buttons and ValueHolder into a parent frame (window?, view?)?

Answer:
Squeak has two different interface models: the older MVC (Model, View, Control), and the newer Morphic. The classes you found are part of MVC. You can use these to build an MVC interface, but it is a little more complicated than Morphic.

This tutorial shows how you can use Morphic to solve this problem.

Preparation


Make sure you are in a Morphic project. If you are not sure about this look here: Starting up Morphic

First approach


Morphic has a direct manipulation interface and a programming facility using tiles. You don't need Smalltalk in the classic way for this. Dan Shafer explains the solution using direct manipulation here: Counter Tutorial

Second approach


Here we will use Smalltalk code to construct the counter. The original description was prepared by Tim Olson.

Daniel McBrearty's comment:
I found Tim's example excellent - if you already know some Smalltalk but want to learn to build morphs from scratch, this is worth exploring. I added comments with a more detailed analysis showing how the LED counter works, step by step. (The only thing I couldn't track was the dependency mechanism - see the query in my code.)


Hannes Hirzel: I reedited the text and added a explanatory note on the 'changed' method.


Tutorial instruction:
Open a new project and then open a new workspace and place it at the right hand side of the screen. Copy all of the following text (until 'third approach') and paste it into the workspace. You can the directly execute the Smalltalk code by selecting it and choosing 'do-it'.


"The object pointed to by the variable frame will be holding the box containing the panel."

frame := AlignmentMorph newColumn."(for version 2.7 and 2.8) or"

frame := AlignmentMorph new listDirection: #topToBottom."(for version 3.0)"



"To make it visible we'll send it the message openInWorld... "


frame openInWorld.
"We now just see a dumb rectangle in the top left corner


We can reposition it by sending it the message position with two coordinate values given by a point:"

frame position: 30 @ 50.


"We now create an LED display for the count"
display := LedMorph new

digits: 4;

extent: (410@150).


" ... to make the display visible we send it as well the message openInWorld. It has all the behaviour of a 4 digit LED display"

display openInWorld.


"it opens on top of frame, but it is draggable ... try it"


"What is it made of? Do print-it on the next line"
display submorphs.

"you should have got something like this (the actual id numbers will be different, every morph get's an id number assigned by the system)

(a LedDigitMorph(2449) a LedDigitMorph(1990) a LedDigitMorph(1560) a LedDigitMorph(2460))"


"Browsing the protocol for LedDigitMorph shows that this is the bit that does the hard work. This class has class variables that define the positions of the segments and the state of each horizontal and vertical segment for each digit and the - sign. It also knows how to change colour and be highlighted or not. Do the following expressions one at a time ... "


(display submorphs at: 2) digit: 5.

(display submorphs at: 2) highlighted: true.




"Note that the second digit was only redrawn when the highlighted message was sent. This is because that method contains the 'self changed' message which causes a redraw.



To test this, modify the 'digit:' instance method. Open a system browser (green window), find the class 'LedDigitMorph' and change the method to look like this ...



digit: anInteger



digit := anInteger \\ 10. ""make sure it stays between 0 and 9""

self changed



.... we have made the individual digit change when its value changes. Test this:"



(display submorphs at: 2) digit: 7.



"A note: The message send 'changed' invokes a method which is implemented in class Morph. It reports that the area should be redrawn. It has nothing to do with the dependency mechanism implemented in class Object which also has a method 'changed'. In Smalltalk-80 this was a heavily used mechanism for building UIs"






"The value of the whole 4-digit array can be changed with the method value::"



display value: 2468.



"Again this uses 'self changed' "



"Now lets add control buttons in a row"

controls := AlignmentMorph newRow. "(for version 2.7 and 2.8) or"

controls := AlignmentMorph new listDirection: #leftToRight."(for version 3.0)"



"how does this look?"

controls openInWorld.



"Drag this away from the previous little rectangle so that the changes will be easy to see.



This is a nice bit. Let's add a quit button"

controls addMorph: (SimpleButtonMorph new

target: frame;

label: 'quit';

actionSelector: #delete).




"increment button"

controls addMorph: (SimpleButtonMorph new

target: display;

label: 'inc';

actionSelector: #increment).




"add a decrement button"

controls addMorph: (SimpleButtonMorph new

target: display;

label: 'dec';

actionSelector: #decrement).




"add a clear button"

controls addMorph: (SimpleButtonMorph new

target: display;

label: 'clear';

actionSelector: #clear).





"We have now three morphs we will assemble
"


"add the controls to our frame"

frame addMorph: controls.


"now add the display"

frame addMorph: display.



"browsing the protocol for LedMorph again shows a couple of ways to change the appearance, straight away ... "



display color: Color red.

display highlighted: true.


We're not ready to run it yet. We have now built the user interface but the we don't have increment/decrement logic is not yet in place. To make this easy, we'll just add it to
the existing LedMorph class, which we are using for the display.



In the browser, find the LedMorph class, go to the "accessing" category,

and find the "value:" method.
We'll use this method as a template for three additional

methods "increment", "decrement", and "clear". Smalltalkers often use existing
code snippets for creating new code.




Now we're ready to give it a try.





Third approach: Separating the UI, the business logic and the application



The following example implementation of the Morphic Counter uses three classes to implement a distinction

Mark Mayfield wrote to the Squeak mailing list on Thu, 19 Aug 1999:

Being someone that is a purest at heart when it comes to separating
business logic, i.e. the model, from its interface, i.e. a view and
controller, here is an alternative to Tim's example. It's a little
more work, but it keeps the LedMorph more of an "interface" object
by making it pluggable. The counter object is a model object, and the counting
application is a cooridnator object in that it creates the counter and pluggable led morph objects and connects them up.
It's not the best code in the world, but it gets the point across.


He also wrote on Monday, 16 Aug 1999
To me a morph is a combination of a view and a controller. I still
create separate model objects that have morphs as dependents.


The three classes





The code:

Select the code, copy it and paste it into a new Workspace. Select it and file it in. Tested with Squeak version 2.7 and 2.8.
For version 3.0 change the instantiation of the AlignmentMorphs in the method openAsMorph in the class 'CounterApplication' as explained under the second approach. (#listDirection: instead of #orientation: ). The code is in the class category 'counting'. You start the application by evaluating
CounterApplication openAsMorph.

Model subclass: #Counter

instanceVariableNames: 'currentValue '


classVariableNames: ''

poolDictionaries: ''

category: 'Counting'!

!Counter methodsFor: 'initialize-release' stamp: 'mlm 8/14/1999

09:30'!



initialize


currentValue _ 0.! !




!Counter methodsFor: 'accessing' stamp: 'mlm 8/14/1999 09:29'!


currentValue


^ currentValue! !

!Counter methodsFor: 'accessing' stamp: 'mlm 8/14/1999 09:29'!


currentValue: anObject

currentValue _ anObject.

self changed: #currentValue! !


!Counter methodsFor: 'counting' stamp: 'mlm 8/14/1999 09:31'!


clear


self currentValue: 0! !

!Counter methodsFor: 'counting' stamp: 'mlm 8/14/1999 09:32'!


decrement

self currentValue: self currentValue - 1! !

!Counter methodsFor: 'counting' stamp: 'mlm 8/14/1999 09:32'!


increment

self currentValue: self currentValue + 1! !

"– – – – – – – – – – – – – – – – – – "!

Counter class

instanceVariableNames: ''!

!Counter class methodsFor: 'instance creation' stamp: 'mlm 8/14/1999

09:34'!



new


^ super new initialize! !



Model subclass: #CounterApplication

instanceVariableNames: 'counter frame display controls '

classVariableNames: ''

poolDictionaries: ''

category: 'Counting'!

!CounterApplication methodsFor: 'user interface' stamp: 'mlm 8/16/1999

13:25'!



openAsMorph

"create a model object"


counter _ Counter new.

"this is going to be the box containing our panel"


frame := AlignmentMorph new

orientation: #vertical.

"create a 4-digit LED display to contain the count"


display := (PluggableLedMorph on: counter value: #currentValue)

digits: 4;

extent: (410@150).

"make the display a dependent of the model"


counter addDependent: display.

"we want our buttons arranged in a horizontal row"


controls := AlignmentMorph new


orientation: #horizontal.

"add a quit button"

controls addMorph:

(SimpleButtonMorph new

target: frame;

label: 'quit';

actionSelector: #delete).

"add an increment button"


controls addMorph:

(SimpleButtonMorph new

target: counter;

label: 'inc';

actionSelector: #increment).

"add a decrement button"

controls addMorph:

(SimpleButtonMorph new

target: counter;

label: 'dec';

actionSelector: #decrement).

"add a clear button"


controls addMorph:

(SimpleButtonMorph new

target: counter;

label: 'clear';

actionSelector: #clear).

"add the controls and display to our panel"


frame addMorph: controls.


frame addMorph: display.

"start playing with it!!"


frame openInWorld! !

"– – – – – – – – – – – – – – – – – – "!

CounterApplication class


instanceVariableNames: ''!

!CounterApplication class methodsFor: 'instance creation' stamp:

'mlm 8/16/1999 13:25'!



openAsMorph


"CounterApplication openAsMorph"

^self new openAsMorph! !




LedMorph subclass: #PluggableLedMorph


instanceVariableNames: 'getValueSelector model '


classVariableNames: ''


poolDictionaries: ''


category: 'Counting'!

!PluggableLedMorph methodsFor: 'initialize-release' stamp: 'mlm

8/16/1999 13:05'!



on: anObject value: aSelector


self model: anObject.


self getValueSelector: aSelector! !



!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:03'!



getValueSelector


^ getValueSelector! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:05'!



getValueSelector: aSelector


getValueSelector _ aSelector! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:02'!



model


^ model! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:05'!



model: aModel


model _ aModel! !



!PluggableLedMorph methodsFor: 'updating' stamp: 'mlm 8/16/1999

13:06'!



update: aSymbol


aSymbol == getValueSelector

ifTrue: [^ self value: (self model perform: self getValueSelector)]! !


"– – – – – – – – – – – – – – – – – –"

PluggableLedMorph class

instanceVariableNames: ''!

!PluggableLedMorph class methodsFor: 'instance creation' stamp:

'mlm 8/16/1999 12:59'!



on: anObject value: aSelector


^ self new on: anObject value: aSelector! !









Source: mailing list archive

topic: "Simple Morphic project".

http://macos.tuwien.ac.at:9009/Server.home







Other version



Janak on Morphic UI: http://www.cc.gatech.edu/fac/mark.guzdial/squeak/morphicui.html





Suggestions for further work





Comments, test notes


Please add comments here....