Chapter 6: Creating a VisualWorks Application

The objective of this chapter is to further illustrate the use of VisualWorks in developing an application by implementing another example application.

The Problem

To objective is to create a phone book application that maintains a list of entries consisting of names and phone numbers. The entries in the list may be updated, added, or deleted. The specifications give more detailed requirements of the application.

Note: We omit error checking for valid data and nicely-formatted displays in this example in order to more quickly illustrate the basic concepts.

The Specifications

The specifications for this application are:

We will use two windows for our application model (GUI). The first window will be the main window that will appear when the application is invoked. This window will include a menu bar, a selection list that displays the customers in alphabetical order by the customer's name, and delete and update buttons to perform actions on the selected customer. The main window will look like the window depicted in Figure 6.1.


Figure 6.1

(Note that there is no "Add" button. We would probably want such a button in an actual application, but here the add function is only put in the menu bar (under "Phone"). Note also that when the application is started the phone book is empty and when the application in closed the phone book is not saved. In an actual application, the initial phone book would probably be obtained from a file, and the phone book would be saved into a file at exit. These features are omitted here in order to simplify the illustration of the implementation.)

The second window will be a dialog window that prompts for the customer name and number for add and update actions. The dialog window will look like the window depicted in Figure 6.2.


Figure 6.2

Now that we have a design for the GUI of our application, we can use VisualWorks and its tools to create the interface.

Creating the Main Window

First, we want to create the main window as it is depicted in Figure 6.1. Use the tools described in Chapter 4 to create the main window. Properties for the widgets depicted in Figure 6.1 are shown in Table 6.3. (Note that the widget with aspect "phoneList" in which the main phone listing will be displayed is a List widget (), not an Input Field widget () as was used for the calculator example in Chapter 4. The name of the widget that is selected is displayed at the bottom of the Palette.

TabPropertySetting
WindowBasics
Basics
Basics
Label:
Enable
Aspect:
ClemsonBell Whitepages
On
menuBar
LabelBasicsLabel:Phone Listing
ListBasicsAspect:phoneList
Action ButtonBasics
Basics
Label:
Action:
Update
updateCustomer
Action ButtonBasics
Basics
Label:
Action:
Delete
deleteCustomer

Table 6.3

Install the Canvas into the PhoneBookInterface class and the windowSpec selector. (Define it as an Application in category UIApplications-New with superclass ApplicationModel.)

Select the list widget and select Define on the Canvas Tool to invoke the UIDefiner to write the method stub for the aspect phoneList. Do the same for the action buttons to define the actions updatePhone and deletePhone.

Build and apply the menu into the menuBar selector and the PhoneBookInterface class, as shown in Figure 6.4. Close the Menu Editor and install the Canvas into the windowSpec Selector. You have now completed the main window. Save your image or file out the PhoneBookInterface class.


Figure 6.4

Creating the Dialog Window

Now we can create the dialog window by selecting widgets from the Palette and placing and arranging them on the Canvas. Create the dialog window as it is depicted in
Figure 6.2. Use the tools described in Chapter 4 to create the dialog interface. Properties for the widgets depicted in Figure 6.2 are shown in Table 6.5. Install the dialog window Canvas into the PhoneBookInterface class and the dialogSpec selector.

TabPropertySetting
WindowBasicsLabel:Add/Update
LabelBasicsLabel:Phone Number
LabelBasicsLabel:Customer Name
Input FieldBasicsAspect:
Type:
Format:
phoneHolder number
Number
(000)000-0000
Input FieldBasicsAspect:
Type:
phoneHolder name
String
Action ButtonBasics
Basics
Label:
Action:
OK
accept
Action ButtonBasics
Basics
Label:
Action:
Cancel
cancel

Table 6.5

Note that the aspect for the Phone Number input field is phoneHolder number, and the aspect for the Customer Name input field is phoneHolder name. The reason for this is that we will define phoneHolder to contain a "customer", which is a name-number pair, and the name and number methods return the appropriate components of the customer. So these two input fields actually only involve one aspect: phoneHolder.

Click on either input field and Define the phoneHolder aspect. It is not necessary to Define the button actions, because accept and cancel are predefined methods for a superclass and need not be redefined. (The accept method updates the phoneHolder instance variable and returns true, while the cancel method just returns false without changing the phoneHolder instance variable.)

You have now completed the dialog window. Save your image or file out the PhoneBookInterface class.

Creating the Smalltalk Classes and Methods

To complete the application we define classes Customer, whose instances are name-phone number pairs, and PhoneBook, each instance of which is a SortedCollection of Customer, for our domain model. Then we complete the application model (interface) class PhoneBookInterface, which represents an application that can be executed.

Customer Class

We begin by creating a class that encapsulates a customer. There are two pieces of information associated with each customer: a phone number and a customer name. The customer class needs methods to access and change these pieces of information.

Class Definition

Open a System Browser and select "UIApplications-New" from the Category View. You should be looking at a template for a class in the Code View. Modify the template to look as follows:


Object subclass: #Customer
	instanceVariableNames: 'name number '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'UIApplications-New'

Choose accept from the Code View [Operate] menu.

Class Comment

Comment the Customer class by selecting comment from the Class View [Operate] Menu and replace the default comment in the Code View Window. Choose accept from the Code View [Operate] menu. Choose definition from the Class View [Operate] menu to return to the class definition.

Instance Methods

Now let's create the instance methods for the class. The Customer class needs methods for accessing the data, operating on the data, and printing the data.
Accessing Protocol Methods
Make sure the instance switch is selected in the Class View. First create a new protocol in the Protocol View by choosing Add... from the Protocol View [Operate] menu. In the dialog box, enter "accessing" and click OK. Now we can create two accessors for the class: name and number. There should be a template for a new method in the Code View. Replace the whole template with the following:


name
	^name

Choose accept from the Code View [Operate] menu. Next, replace the code in the Code View with


number
	^number

and choose accept from the Code View [Operate] menu.

We also need mutators to change the value of name or number. Edit the code in the Code View and choose accept from the Code View [Operate] menu for each of the following:


name: aName
	name := aName


number: aNumber
	number := aNumber


name: aName number: aNumber
	self name: aName.
	self number: aNumber

Printing Protocol Methods
Next, add a new protocol called "printing". Replace the template in the Code View with the following:


printOn: aStream
	aStream nextPutAll: name, ' - ', number displayString

This method is used to display each customer in the List widget of the main GUI window. The printOn: method is invoked when a SortedCollection is converted to a SelectionInList in order to display the items of the SelectionInList. As we shall see soon, we will use a SortedCollection as the container class for our customers, and the List widget will be a SelectionInList to allow us to select individual items from the list.

Choose accept from the Code View [Operate] menu.

Operators Protocol Methods
The ordering in a SortedCollection, which we will use as the container class for our phone book customers, is done using the "<=" comparison operator. Thus we need to define <= for Customer instances. Add a new protocol called "operators", and add the <= method to this protocol:


<= aCustomer
	^self name <= aCustomer name

Note that this orders the customers in alphabetical order on the name component of each customer (as a character string). Thus, for example, in order to have the entries listed in order of last (family) name, the last name must begin the name string for each customer.

Choose accept from the Code View [Operate] menu.

Class Methods

We need class methods for creating new instances of class Customer. Make sure that the class switch is selected in the Class View, then Add... the new protocol "instance creation" for the Customer class. Now we create two instance creation methods, new and name:number:.

There should be a template for a method in the Code View. Replace the whole template with the following:


new
	"Create a new Customer."
	^(super new) name: '' number: 0

Choose accept from the Code View [Operate] menu. Next, replace the code in the Code View with


name: aName number: aNumber
	"Create a new Customer (initialized)."
	^(super new) name: aName number: aNumber

and choose accept from the Code View [Operate] menu. This completes the class methods for class Customer.

The Customer class is now complete. You may wish to save your image or file out the category UIApplications-New. (The category UIApplications-New is filed out rather than the class Customer because the application now consists of more than one class.)

PhoneBook Class

An instance of the PhoneBook class will contain a sequence (SortedCollection) of Customers. This class is the basic domain model class for the phone book listing that will be associated with an instance of the main GUI window that we designed at the beginning of this chapter.

Class Definition

The class definition for the PhoneBook is given below.


Model subclass: #PhoneBook
	instanceVariableNames: 'whitepages'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'UIApplications-New'

Note that the superclass here is "Model" instead of "Object". The Model class is similar to Object, but it has some restrictions that are more appropriate for domain model classes. Our implementation would also work if we used "Object" as the superclass here.

Class Comment

Comment the PhoneBook class by selecting comment from the Class View [Operate] Menu and replace the default comment in the Code View Window. Choose accept from the Code View [Operate] menu. Choose definition from the Class View [Operate] menu to return to the class definition.

Class Methods

Add the following class method for instance creation to the PhoneBook class.
new
	^super new initialize

Instance Methods

Add the instance methods shown below for the PhoneBook class.
Initialize Protocol Methods

initialize
	whitepages := SortedCollection new

(Note that this method initializes the whitepages instance variable to an instance of class SortedCollection. Customers will be added to the SortedCollection maintaining the order specified by the <= operator on Customer instances.)
Accessing Protocol Methods

whitepages
	^whitepages

This method just allows a user to obtain the SortedCollection of customers in a PhoneBook instance.

Transactions Protocol Methods
The following two methods allow a user to add customers to, and delete customers from, a PhoneBook instance. These operations are done by adding to, or removing from, the SortedCollection of Customers in the instance variable whitepages.


addCustomer: aCustomer
	whitepages add: aCustomer


deleteCustomer: aCustomer
	whitepages remove: aCustomer

This completes the PhoneBook class. You can save your image or file out the category, if you wish, before completing the remainder of the phone book implementation.

PhoneBookInterface Class

The PhoneBookInterface class provides instances of the GUI windows that we created at the beginning of the chapter to provide access to a phone book. The final step in the phone book implementation is to complete the code for this class.

Class Definition

We first add an instance variable, phonebook, to our interface class to hold the instance of PhoneBook that is associated with the interface window. Modify the Class Definition for PhoneBookInterface to appear as follows:


ApplicationModel subclass: #PhoneBookInterface
	instanceVariableNames: 'phoneList phoneHolder phonebook'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'UIApplications-New'

Class Comment

Comment the PhoneBookInterface class by selecting comment from the Class View [Operate] Menu and replace the default comment in the Code View Window. Choose accept from the Code View [Operate] menu. Choose definition from the Class View [Operate] menu to return to the class definition.

Instance Methods

Complete the instance methods for class PhoneBookInterface as shown below.

Initialize Protocol Methods
Create an initialize protocol and add the following method:


initialize
	phonebook := PhoneBook new

Aspects Protocol Methods
The UIDefiner created the aspects protocol and initializing code for phoneList and phoneHolder. Modify the code for the two methods to appear as shown below.


phoneList
	^phoneList isNil
	ifTrue: [phoneList := SelectionInList with: phonebook whitepages]
	ifFalse: [phoneList]

(This method causes phoneList to be initialized to a SelectionInList created from the SortedCollection of Customer instances in phonebook. Recall that the phoneList aspect is associated with the Phone Listing widget in the main GUI window. So the effect of this is to initialize the Phone Listing widget display (as well as the instance variable phoneList) to a SelectionInList that is obtained by converting the SortedCollection of Customer instances to a SelectionInList. The SelectionInList class (whose instance variables are ValueHolders) allows any individual item in the list (a customer in this case) to be selected. Each item in the Phone Listing widget is displayed as specified by the printOn: method for Customer instances.)


phoneHolder
	^phoneHolder isNil
	ifTrue: [phoneHolder := Customer new asValue]
	ifFalse: [phoneHolder]

This method just ensures that phoneHolder is a ValueHolder containing an instance of Customer. Recall that the two windows of the Add/Update dialog window display the number and name components of the Customer instance.

Actions Protocol Methods
The UIDefiner created the actions protocol and initializing code for deleteCustomer and updateCustomer. Modify the code for these methods as shown below.


deleteCustomer
	phoneList selection isNil
		ifTrue: [^Dialog warn: 'Select a customer to delete.'].
	phonebook deleteCustomer: phoneList selection.
	phoneList list: phonebook whitepages

This method first checks to see if there is a customer selected to be deleted. The selection message sent to a SelectionInList instance (which the value of phoneList is) returns the item in the list that is selected (highlighted), or nil if there is nothing selected. If the selection is nil, then a message is sent to the user by sending a warn: message to the Dialog class, and the deleteCustomer method is terminated (by a return). (The warning message dialog window remains open, and control remains in the dialog window, until the user clicks on "OK".) If a customer is selected, then the selected customer (phoneList selection) is sent as a parameter with the deleteCustomer: method to phonebook, and the Phone Listing widget display (instance variable phoneList) is updated to show the phone book listing after the deletion.


updateCustomer

	phoneList selection isNil
		ifTrue: [^Dialog warn: 'Select a customer to update.'].
	phoneHolder value: phoneList selection.
	(self openDialogInterface: #dialogSpec)
		ifTrue: [phonebook deleteCustomer: phoneList selection.
			phonebook addCustomer: 
					(Customer name: phoneHolder value name
						number: phoneHolder value number).
			phoneList list: phonebook whitepages]

Here the method first checks to make sure that there is a selection to update, just as was done in deleteCustomer. If there is a selection, then the Add/Update input widgets (phoneHolder) are updated to show the name and number of the selected customer (phoneHolder value: phoneList selection). (Recall that phoneHolder is a ValueHolder, and so its value is changed by the value: method.)

Next, the method opens the dialog window that was created and named "dialogSpec" (this is the Add/Update window) by sending the openDialogInterface: message to self. (Recall that these are methods for class PhoneBookInterface, so self is an instance of PhoneBookInterface, which we defined as a subclass of ApplicationModel. Because we have defined no method openDialogInterface:, we could just as well use "super" here instead of "self".) The openDialogInterface: method causes a window whose name is specified in the parameter ("dialogSpec" in this case) to be opened, and a value of true or false is returned, depending on whether the dialog window is terminated by OK (true) or Cancel (false). (Note that the dialog window parameter must be specified as the symbol #dialogSpec rather than as the variable dialogSpec, because the parameter is evaluated and its value is used in the method openDialogInterface:. The value of #dialogSpec is the name "dialogSpec", which is what is desired. The variable dialogSpec would be undefined.)

If the result from the Add/Update dialog window is true, then the updateCustomer method deletes the selected customer and adds a new customer whose name and number are those from the (updated) phoneHolder that resulted from the Add/Update dialog. Finally, the Phone Listing widget in the main window is updated to reflect the updated list of customers.

No action method stub for adding a new customer was created by the UIDefiner because the Add action is only specified from the menu bar, so we have to add the addCustomer method that was specified in the menu bar as the action for the Add menu option. Add the addCustomer method as it appears below:


addCustomer

	phoneHolder := nil.
	(self openDialogInterface: #dialogSpec)
		ifTrue: [phonebook addCustomer: 
				(Customer name: phoneHolder value name
					number: phoneHolder value number).
			phoneList list: phonebook whitepages]

This method is much like the previous one. The phoneHolder is set to nil so that it will receive the default initialization (null string for the name and 0 as the number) as the initial values of the Add/Update input windows. The remainder is the same as the updateCustomer method, except that no customer is deleted.

Using the Application

This completes the phone book application. If you wish to save your work, file out the UIApplications-New category, which contains the three classes Customer, PhoneBook, and PhoneBookInterface. (You can also save the image.)

To run the application, use a Resource Finder to select the PhoneBookInterfaceClass and the windowSpec selector, and then select Start. Close the Resource Finder. (Remember that to add a new customer you have to choose the Phone->Add selection from the menu bar.) Choose File->Exit from the phonebook application menu to close the application.