VisualWorks UI Slides
P | N
- Creating User Interfaces in VisualWorks
- To do PA3, you can:
- Use the Coad & Nicola library. Chapter 3 introduces using lists.
- Use VisualWorks as-is. Requires better understanding of MVC
- We're going to discuss using VisualWorks as-is
- How MVC Works: It uses a general dependency mechanism
- UI without MVC
- A Big MVC Application: ContactManager (see ContactManager.st)
- Running the ContactManager
- Dependency Mechanisms in VisualWorks
- Described in Chapter 27 of Hopkins and Horan

- You can set up dependencies between any two objects
- Can trigger an update based on a
self changed: #aspect
- Reaching the Dependents
- You can reach the dependents using several different commands:
- changed:
- changed:with:
- broadcast #someMessage
- broadcast #someMessage: with: aParameter
- Dependency Implementation
- All objects can have dependents!
- But kept in a global dictionary => SLOW!
- Models keep dependents in a local instance variable
- dependents
- Allows for much faster updates
- UI without MVC: Menus
- (Chapter 28, Hopkins & Horan)
- How you do it in VisualWorks:
- How you do it in Squeak:
- UI without MVC: Dialogs
- Getting simple information from the user
- In VisualWorks:
- Dialog request: 'User name:'

- In Squeak:
- Benefits of MVC
- Separates the UI from the underlying structure
- Views present some aspect of the Model. (Can be multiple views of same model.)
- (Chapter 29 Hopkins & Horan)
- Process of Creating an MVC Application
- 1. Build domain model
- 2. Build application model (may be the same)
- 3. Build the views. Support displayOn:
- 4. Arrange views in a window
- 5. Build or connect controller for each view (e.g., for menus)
- 6. Create an opening method. Create a ScheduledWindow, arranges things in it, and then send the window open
- Our Example Application: ContactManager
- Suggested activities at this point:
- Load up VisualWorks and try the ContactManager code
- Try the non-MVC dialog classes: Menus, dialogs
- ContactManager Class
- Model subclass: #ContactManager
- instanceVariableNames: 'contacts names currentName company phone '
- classVariableNames: ''
- poolDictionaries: ''
- category: 'ContactManager Project'
- ContactManager class methodsFor: 'instance creation'
- new
- ContactManager methodsFor: 'initialize-release'
- initialize
- "Note technique: Assignment returns object, so can send messages to it."
- (contacts := Dictionary new)
- add: ('Jobs, Steve'->#('Next Computer' '332-9803'));
- add: ('West, Mae'->#('Playtex Inc.' '445-9063'));
- add: ('Adams, Gomez'->#('Independent Eccentric' '999-1313')).
- names := contacts keys asSortedCollection. "names are just the keys, in sorted order."
- ContactManager accessing
- ContactManager methodsFor: 'accessing'
- company
- company: aString
- | array |
- company := aString.
- "Array will always be something: the right one, or a new one."
- array := self contacts at: self currentName ifAbsent: [Array new: 2].
- array at: 1 put: aString. "Note: This actually changes the dictionary! Shared pointer!"
- ^true
- contacts
- ContactManager currentName
- "Keeps track of the selection in the listbox"
- currentName
- currentName: aString
- "When the currently selected name changes,
- set the fields to their new values and update views."
- | array |
- currentName := aString.
- array := self contacts at: aString ifAbsent: [Array new: 2].
- self company: (array at: 1).
- self changed: #company.
- self phone: (array at: 2).
- self changed: #phone
- ContactManager add
- A non-MVC User Interface
- add
- "Use a dialog to prompt the user for a new contact name.
- Update the contacts dictionary, the currentName, and the
- names collection appropriately."
- | reply |
- (reply := DialogView request: 'Name of new contact') isEmpty
- ifTrue: [^nil].
- self contacts at: reply put: (Array new: 2).
- self currentName: reply.
- self names: self contacts keys asSortedCollection.
- What it looks like:
- ContactManager delete
- delete
- "Make sure the user wants to remove the current
- contact and then update the contacts dictionary,
- currentName, and names collection appropriately."
- self currentName isNil ifTrue: [^nil].
- (DialogView confirm: 'Remove this contact?')
- self contacts removeKey: self currentName.
- self currentName: nil.
- self names: self contacts keys asSortedCollection
- ContactManager open
- "This is the method we used to open that window (previous)"
- open
- "self open"
- ^self openOn: self new
- ContactManager openOn:
- openOn: aContactManager
- "Open a user interface for the model aContactManager."
- | window component view layout wrapper |
- "NOTICE! ALL LOCAL VARIABLES!"
- window := ScheduledWindow new "This will be our window"
- model: aContactManager; "On this model"
- label: 'Contact Manager';
- minimumSize: 300@150.
- component := CompositePart new. "component will hold all the pieces"
- ...
- Making the SelectionList
- "We're still in openOn:"
- "Things to note: Using pixel values for offsets, decimal fractions for layout, and LookPreferences"
- view := SelectionInListView on: aContactManager
- aspect: #names "This is what the list cares about."
- change: #currentName: "This is what the list sends with the selected name when there's a selection"
- list: #names "This is what the list sends to get the list. NOTE: Doesn't have to be the same as the aspect!"
- menu: nil "Do we want a special menu?"
- initialSelection: #currentName. "Which one to select first?"
- layout := LayoutFrame leftFraction: 0.0
- offset: 10
- rightFraction: 0.5
- offset: -10
- topFraction: 0.0
- offset: 10
- bottomFraction: 1.0
- offset: -10.
- wrapper := LookPreferences edgeDecorator on: view.
- component add: wrapper in: layout. "Finally, add it into the component (CompositeView)"
- ...
- And what's a LookPreference?
- The LookPreference sets up the UI "look": Color, shading, etc. (Different for different platforms.)
- The class comment (middle-button on the classname in the pane):
- Class LookPreferences has colors to be used for border color, foreground color, background color, selection foreground color, selection background color, hilite color, and shadow color. Class LookPreferences answers a changed copy for modification messag
- Instance Variables:
- foregroundColor color to be used for the foreground backgroundColor color to be used for the backgrond selectionForegroundColor color to be used for the foreground of a selection
- selectionBackgroundColor color to be used for the background of a selection
- borderColor color to be used for borders
- hiliteColor color to be used for simulating direct illumination of a raised surface
- shadowColor color to be used for simulating indirect illumination of a recessed surface
- Adding the Company label
- "Turn the string into a text (with style), bold face it, then as a display text."
- view := 'Company' asText allBold asComposedText.
- layout := LayoutFrame
- leftFraction: 0.5
- offset: 10
- rightFraction: 1.0
- offset: -10
- topFraction: 0.0
- offset: 10
- bottomFraction: 0.0
- offset: 31.
- component add: view in: layout.
- ...
- Adding the Company Textfield
- view := TextView on: aContactManager
- aspect: #company "Update upon this message"
- change: #company: "Send this if/when the field changes"
- menu: nil.
- view controller initializeMenuForText. "Make it the standard text menu: Copy/paste, etc."
- layout := LayoutFrame leftFraction: 0.5
- offset: 10
- rightFraction: 1.0
- offset: -10
- topFraction: 0.0
- offset: 30
- bottomFraction: 0.0
- offset: 56.
- wrapper := (LookPreferences edgeDecorator on: view) noVerticalScrollBar.
- component add: wrapper in: layout.
- ...
- Adding the Add Button
- "Buttons use PluggableAdaptor as a model (which opens on the ContactManager)
- that perform their action when triggered."
- view := Button trigger model: ((PluggableAdaptor on: aContactManager) performAction: #add);
- label: 'Add'.
- layout := LayoutFrame leftFraction: 0.5
- offset: 10
- rightFraction: 0.5
- offset: 70
- topFraction: 1.0
- offset: -40
- bottomFraction: 1.0
- offset: -10.
- wrapper := BoundedWrapper on: view. "Makes the button look like a button."
- component add: wrapper in: layout.
- Doing the open
- "Finally, at the bottom of the openOn: method, we actually open the window"
- (We skipped: Phone stuff, Delete button)
- component add: wrapper in: layout. "Put the last piece in the component"
- window component: component. "Add the component into the window"
- ^window open. "Open the window"
- NOTE: And now, the window reference is gone! (All local variables.)
- But it still exists in the internal list of ScheduledWindows
- And all the pieces are in the window
- So, nothing gets garbage collected, but there are no our-program-side references anymore
- (Except hidden in the dependency list.)
- Now that we have it all entered, let's start changing it!
- Let's figure out what's happening...
- currentName: aString
- "When the currently selected name changes,
- set the fields to their new values and update views."
- | array |
- currentName := aString.
- array := self contacts at: aString ifAbsent: [Array new: 2].
- self company: (array at: 1).
- Transcript cr; show: 'Changing company!'.
- self changed: #company.
- self phone: (array at: 2).
- Transcript cr; show: 'Changing phone!'.
- self changed: #phone
- And the update side...
- phone
- Transcript show: 'Being asked for phone.'.
- ^phone
- company
- Transcript show: 'Being asked for company.'.
- ^company
- ...And from the Caller.
- add "Use a dialog to prompt the user for a new contact name.
- Update the contacts dictionary, the currentName, and the
- names collection appropriately."
- | reply |
- (reply := DialogView request: 'Name of new contact') isEmpty ifTrue: [^nil].
- Transcript show: 'ADD: Got the reply'.
- self contacts at: reply put: (Array new: 2).
- Transcript show: 'ADD. Creating the currentName.'.
- self currentName: reply.
- Transcript show: 'ADD. Now, store the empty contact info.'.
- self names: self contacts keys asSortedCollection.
- Here's the Transcript:
- "Trace this through - note when things are happening without explicit calls"
- Changing company!
- Changing phone!
- Being asked for company.
- Being asked for phone.
- ADD: Got the reply
- ADD. Creating the currentName.
- Changing company!
- Being asked for company.
- Changing phone!
- Being asked for phone.
- ADD. Now, store the empty contact info.
- Changing company!
- Being asked for company.
- Changing phone!
- Being asked for phone.
Previous | Next
Last modified at 8/13/97; 4:49:00 PM
Other Links of Interest
College of Computing | EduTech Institute | GVU Center
Mark Guzdial | Papers | CS 2390 Spring '97 Home Page | STABLE | MMC-CaMILE
Slide Master