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.
Tab | Property | Setting | |
---|---|---|---|
Window | Basics Basics Basics | Label: Enable Aspect: | ClemsonBell Whitepages On menuBar |
Label | Basics | Label: | Phone Listing |
List | Basics | Aspect: | phoneList |
Action Button | Basics Basics | Label: Action: | Update updateCustomer |
Action Button | Basics Basics | Label: Action: | Delete deleteCustomer |
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.
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 Basics | Label: Action: | OK accept |
Action Button | Basics Basics | Label: Action: | Cancel cancel |
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.
Object subclass: #Customer instanceVariableNames: 'name number ' classVariableNames: '' poolDictionaries: '' category: 'UIApplications-New'
Choose accept from the Code View [Operate] menu.
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
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.
<= 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.
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.)
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.
new ^super new initialize
initialize whitepages := SortedCollection new
whitepages ^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.
ApplicationModel subclass: #PhoneBookInterface instanceVariableNames: 'phoneList phoneHolder phonebook' classVariableNames: '' poolDictionaries: '' category: 'UIApplications-New'
initialize phonebook := PhoneBook new
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.
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.
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.