Note: We omit error checking for valid data and nicely-formatted displays in this example in order to more quickly illustrate the basic concepts.
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.
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.
Table 6.3
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.
Table 6.5
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.
Choose accept from the Code View [Operate] menu.
Choose accept from the Code View [Operate] menu. Next, replace the code
in the Code View with
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:
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.
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.
There should be a template for a method in the Code View. Replace the whole template with the following:
Choose accept from the Code View [Operate] menu. Next, replace the code in the Code View with
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.)
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.
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.
(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.)
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.
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.
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:
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.
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.
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. The Specifications
The specifications for this application are:
Figure 6.1
Figure 6.2Creating 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.
Tab Property Setting
Window Basics
Basics
BasicsLabel:
Enable
Aspect:ClemsonBell Whitepages
On
menuBar
Label Basics Label: Phone Listing
List Basics Aspect: phoneList
Action Button Basics
BasicsLabel:
Action:Update
updateCustomer
Action Button Basics
BasicsLabel:
Action:Delete
deleteCustomer
Figure 6.4Creating 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.
Tab Property Setting
Window Basics Label: Add/Update
Label Basics Label: Phone Number
Label Basics Label: Customer Name
Input Field Basics Aspect:
Type:
Format:phoneHolder number
Number
(000)000-0000
Input Field Basics Aspect:
Type:phoneHolder name
String
Action Button Basics
BasicsLabel:
Action:OK
accept
Action Button Basics
BasicsLabel:
Action:Cancel
cancel
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'
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
number
^number
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
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
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:.
new
"Create a new Customer."
^(super new) name: '' number: 0
name: aName number: aNumber
"Create a new Customer (initialized)."
^(super new) name: aName number: aNumber
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'
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
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]
phoneHolder
^phoneHolder isNil
ifTrue: [phoneHolder := Customer new asValue]
ifFalse: [phoneHolder]
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
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]
addCustomer
phoneHolder := nil.
(self openDialogInterface: #dialogSpec)
ifTrue: [phonebook addCustomer:
(Customer name: phoneHolder value name
number: phoneHolder value number).
phoneList list: phonebook whitepages]
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.)