The PluggableListMorph displays a scrollable list of items. Users can select an item by clicking it or by typing its first few letters.
Let’s create a small application that allows users to select a color from a list to change the background color of the window. Users can also add new colors and delete existing colors.
In addition to demonstrating the use of PluggableListMorph, we will also see how to disable buttons when their use is not appropriate.
Figure 4.11: List Demo
Create the following class:
Object subclass: #ListDemo instanceVariableNames: 'colorList colors deleteButton newColorEntry selectedColorIndex selectedLabel window' classVariableNames: '' poolDictionaries: '' category: 'Demos'
Add the following instance methods. We start with a bit tool long initialize method responsible to build the whole GUI.
initialize
| addButton layout row | colors := SortedCollection newFrom: #(red orange yellow green blue purple). selectedColorIndex := 0. colorList := PluggableListMorph withModel: self listGetter: #colors indexGetter: #selectedColorIndex indexSetter: #selectedColorIndex:. colorList layoutSpec proportionalWidth: 1. newColorEntry := self textEntryOn: #newColor. newColorEntry emptyTextDisplayMessage: 'new color'. addButton := PluggableButtonMorph model: self action: #addColor label: 'Add'. row := LayoutMorph newRow gap: 10; addMorph: newColorEntry; addMorph: addButton. deleteButton := PluggableButtonMorph model: self action: #deleteColor label: 'Delete Selected Color'. selectedLabel := LabelMorph contents: ''. window := SystemWindow new. window setLabel: 'List Demo'; addMorph: colorList; addMorph: row; addMorph: deleteButton; addMorph: selectedLabel; openInWorld. "sets initial background color" self selectedColorIndex: 0. "Set window size to the smallest height that contains its submorphs." layout := window layoutMorph. layout separation: 10. window morphExtent: 250 layout minimumExtent y
Observe at the begining of the initialize method the creation of the list, its model is self, therefore an instance of ListDemo. In a real application it should be a model object representing some type of data. Associated to the model are the messages to send to self to retrieve the list contents (#colors), to get the index of the selected entry in the list (#selectedColorIndex) and to set the selected index (#selectedColorIndex:).
The methods responding to these message are implemented in ListDemo:
addColor
self newColor: newColorEntry text
colors
^ colors
deleteColor
selectedColorIndex = 0 ifFalse: [ colors removeAt: selectedColorIndex. self selectedColorIndex: 0. colorList updateList. selectedLabel contents: '' ]
newColor
"In this app there is no need to retrieve this value or even hold it in an instance variable, but TextModelMorph requires that this method exists." ^ ''
When a new color is input by the user, we add it to the colors collection, update the list then select this color from the list. To prepare for any additional new color input, we clear the text entry by emitting the event #newColor.
newColor: aText
| potentialColor | potentialColor := aText asString withBlanksTrimmed. potentialColor ifNotEmpty: [ colors add: potentialColor asSymbol. colorList updateList. self selectedColorIndex: (colors indexOf: potentialColor ) self changed: #clearUserEdits. self changed: #newColor ]
Remember, in Smalltalk index of a collection naturally starts with one. Therefore, an index of zero naturally indicates that no item is selected in the collection.
selectedColorIndex
^ selectedColorIndex
This method is called when the user clicks or unclicks an item of the list. As recalled earlier, an index of zero indicates that no item was selected. If the color can not be determined by the name added to the list or is empty, a default gray color is set as the background color of the window.
selectedColorIndex: anIndex
| color colorName selected | selectedColorIndex := anIndex. selected := anIndex ~= 0. deleteButton enable: selected. colorName := selected ifTrue: [ colors at: anIndex ]. selectedLabel contents: (colorName ifNil: [''] ifNotNil: [ 'You selected {1}.' format: { colorName } ] ). color := colorName ifNil: [ Color gray ] ifNotNil: [ [ Color perform: colorName ] on: MessageNotUnderstood do: [ Color gray ] ]. window layoutMorph color: (color alpha: 0.6)
textEntryOn: aGetter
"Answer a TextModelMorph where aGetter provides the symbol for the getter." | entry | entry := TextModelMorph textProvider: self textGetter: aGetter textSetter: (aGetter, ':') asSymbol :: acceptOnCR: true; askBeforeDiscardingEdits: false; hideScrollBarsIndefinitely. entry morphExtent: 0 @ 0. entry layoutSpec proportionalWidth: 1. ^ entry