parent previous next question (Smalltalk Textbook 16)

PluggableMVC

'PluggableMVC' is a useful mechanism for Smalltalk programming. I describe 'PluggableMVC' in preparation for the next two sections in which I will cover styles of characters then make a text editor with which you can specify text justification, indentation, spacing, tab, font, and color. If you do not understand 'PluggableMVC', you will not understand the text editor.

You frequently need to offer the user a single item from a list. This feature is provided as a VC called 'SelectionInListView' and 'SelectionInListController' in Smalltalk. a VC already exists, so you only need to make a M to complete an MVC. Usage of a VC is:

---------------------------------------------------------
	SelectionInListView
		on: model
		aspect: a message to get a selected item
		change: a message to set up a selected item
		list: a message to get a list to select
		menu: a message to get a the yellow button menu
		initialSelection: a message to get a first selected item
----------------------------------------------------------

A model which understands the above 5 mesages can use the SelectionInListView to allow the user to choose one item from a list.

'TextView' and 'TextController' are also useful. These support text editing. Usage of 'Textview':

-------------------------------------------------------
	TextView
		on: model
		aspect: a message to get a text
		change: a message to set up a text
		menu: a message to get a the yellow button menu
		initialSelection:  first selected text
--------------------------------------------------------

Any model which understands the above 3 messages can serve as text editor. We call these massages 'pluggable messages', because we are giving only their names.

Usage of thses VCs illustrates 'PluggableMVC'. Pluggable means that M and VC is connected by a few messages and M sends messages to VC. The origin of the word 'PluggableMVC' is taken from plugging a M into a socket, the VC.

Program 16-1 is a dummy model to implement 'PluggableMVC' which uses above VCs. Write Program 16-1 to a file by 'file list' then 'file in' to the system. 'EngiDummyModel' is a temporary class, so delete it after finishing this section.


Program-16-1: (Model, PopUpMenu, EngiDummyModel; textMenu, itemMenu, PluggableMVC)
-----------------------------------------------------
Model subclass: #EngiDummyModel
	instanceVariableNames: 'item items text '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Engi-Tutorial'!


!EngiDummyModel methodsFor: 'list adaptor'!

item
	^item!

item: selection 
	item := selection.
	self changed: #itemSelection!

itemMenu
	^PopUpMenu labelArray: #('inspect' ) values: #(#inspect )!

items
	^items!

items: aCollection 
	items := aCollection.
	self changed: #item!

itemSelection
	^self item! !

!EngiDummyModel methodsFor: 'text adaptor'!

text
	^text!

text: aText 
	text := aText asText.
	self changed: #text.
	^true!

textMenu
	^PopUpMenu
		labelArray: #('again' 'undo' 'copy' 'cut'
				'paste' 'accept' 'cancel' )
		lines: #(2 5 )
		values: #(#again #undo #copySelection #cut
				#paste #accept #cancel )! !
---------------------------------------------------------

This dummy model is too simple to need much explanation, so I will just point out a few key things. Three instance variables are 'item' (holds the selected item), 'items' (holds the whole list) and 'text' (holds a text). 'item' and 'itemSelection' messages seem redundant, but they are useful when more than one view-controller pair are connected to the same model.

Also notice the 'changed:' messages for broadcasting when a model is changed. Program 16-2 is an example program which uses 'EngiDummyModel'. Before I explain the program, please evaluate Program 16-2 and see what happens.


Program-16-2: (Model, SelectionInListView, EngiTopView, TextView, 
EngiDummyModel; textMenu, itemMenu, PluggableMVC)
----------------------------------------------------
| aModel listViewBlock textViewBlock topViewBlock |
aModel := EngiDummyModel new.
aModel item: 'VisualWorks'.
aModel items: #(
		'VisualWorks'
		'ObjectWorks'
		'Smalltalk/V'
		'SmalltalkAgents'
		'GNU Smalltalk' ).
aModel text: 'Smalltalk Family'.
listViewBlock := 
	[| listView aDecorator |
	listView := SelectionInListView
				on: aModel
				aspect: #item
				change: #item:
				list: #items
				menu: #itemMenu
				initialSelection: #itemSelection.
	aDecorator := LookPreferences edgeDecorator on: listView.
	aDecorator noMenuBar.
	aDecorator useVerticalScrollBar.
	aDecorator noHorizontalScrollBar.
	aDecorator].
textViewBlock := 
	[| textView aDecorator |
	textView := TextView
				on: aModel
				aspect: #text
				change: #text:
				menu: #textMenu
				initialSelection: 'Smalltalk'.
	aDecorator := LookPreferences edgeDecorator on: textView.
	aDecorator noMenuBar.
	aDecorator useVerticalScrollBar.
	aDecorator noHorizontalScrollBar.
	aDecorator].
topViewBlock := 
	[| topView |
	topView := EngiTopView
				model: nil
				label: 'List and Text'
				minimumSize: 200 @ 200.
	topView add: listViewBlock value in: (0 @ 0 corner: 1 @ 0.5).
	topView add: textViewBlock value in: (0 @ 0.5 corner: 1 @ 1).
	topView].
2 timesRepeat: [topViewBlock value open].
----------------------------------------------------

You should see two windows open with 'List and Text' as their label. The windows contain a list view and a text view. 'VisualWorks' is highlighted in the list view and 'Smalltalk' is highlighted in the text view. See how this is accomplished in the 'initialSelection:' Pluggable message.

If you press the yellow button in one of the list views, you should see a menu with just one item, namely "inspect". Also in the text view, you have access to an edit menu via the yellow button. Because 'itemMenu' and 'textMenu' message were specified as arguments for the pluggable message ('menu:'), those messages are sent to the dummy model when the yellow button is pressed and they return menus.

To see that the two windows are both connected to the same model, please select several different items (ex. 'ObjectWorks') in the list view. Both list views are updated. The 'item:' argument of the pluggable 'change:' message is sent to the dummy model with a new selected item when you press the red button in the list view to change the selection. The dummy model stores the selected item in an instance variable named "item", then propagates the change via the 'self changed:' message. Other views receive the update, and adjust themselves accordingly. Notice the argument of the 'changed:' message.

Next, change some text in the text view and select 'accept' in the yellow button menu and you will see the text views of both windows change. The 'text:' argument of the pluggable 'change:' message is sent to the dummy model when 'accept' is chosen from the menu. The dummy model stores the new text to an instance variable named "text", then notifies itself of the change. Any dependent views are notified via the "update" mechanism, and each adjusts itself accordingly. Notice that the dummy model returns a boolean value of true. (?? bnr: but it works even if it is changed to return 'false')

You must see a inspector for the dummy model by select 'inspect' of the yellow button menu in the list view. Execute the following message expression one by one in the inspector. 'self' means the dummy model itself.

-----------------------------------------------------
	self item: nil
	self item: 'ObjectWorks'
	self items: #('yesterday' 'today' 'tomorrow'); item: 'today'
	self text: 'Smalltalk is my favorite.'
-----------------------------------------------------

You can see that it takes only a few messages to make a model that can be accessed from pre-existing VC classes. Here is a piece of advice for the effective usage of MVC in Smalltalk. Make a model which can be accessed from more than just one VC pair (view and controller). This will force you to think about the services the model provides apart from any interfaces used to view and control the model. Testing a model via only one view is not real testing. Develop the habit of testing your models with more than one view.


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves