User Interface Design Patterns
Login Example
For this lecture, we will use the application with interface as
The UI code is
package ui;
import java.awt.*;
import javax.swing.*;
class Login extends JFrame {
static final int TEXT_WIDTH = 15;
JTextField uid;
JPasswordField passwd;
public static void main(String argv[]) {
new Login();
}
Login() {
super("Login Form");
Container contents = getContentPane();;
JPanel loginPanel = getLoginPanel();
contents.add(loginPanel, BorderLayout.CENTER);
JPanel buttonsPanel = getButtonsPanel();
contents.add(buttonsPanel, BorderLayout.SOUTH);
pack();
setVisible(true);
}
JPanel getLoginPanel() {
JPanel panel = new JPanel();
GridBagLayout gridbag = new GridBagLayout();
panel.setLayout(gridbag);
GridBagConstraints c = new GridBagConstraints();
c.weightx = 0.0;
c.anchor = GridBagConstraints.EAST;
GridBagConstraints c2 = new GridBagConstraints();
c2.gridwidth = GridBagConstraints.REMAINDER;
c2.weightx = 1.0;
c2.fill = GridBagConstraints.HORIZONTAL;
JLabel uidLabel = new JLabel("Uid");
uid = new JTextField(TEXT_WIDTH);
JLabel passwdLabel = new JLabel("Password");
passwd = new JPasswordField(TEXT_WIDTH);
panel.add(uidLabel);
panel.add(uid);
panel.add(passwdLabel);
panel.add(passwd);
gridbag.setConstraints(uidLabel, c);
gridbag.setConstraints(uid, c2);
gridbag.setConstraints(passwdLabel, c);
gridbag.setConstraints(passwd, c2);
return panel;
}
JPanel getButtonsPanel() {
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
JButton okButton = new JButton("OK");
panel.add(okButton);
return panel;
}
}
Note that this only gives the GUI objects and their layout, no behaviour
is associated with them
Validation
- You need to check if fields entered are okay
- The checks may be syntactic or semantic
- Syntactic checks are e.g."Does the entered text just consist
of digits?"
- Semantic checks are e.g. "Is this a valid login id?"
- Syntactic checks are handled on the UI side
- Semantic checks require use of the model, and should not be
performed by UI code
- Checks related to business rules should also be done on the mode
side, not the UI side
Validator Pattern
Validator Example
- When the focus is lost on the uid field, check that it
consists of four digits only
- Class diagram 1
- Class diagram 2
- Class diagram 3
Implementation of third option
package ui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DigitsOnlyTextField extends JTextField
implements FocusListener, Validator {
final int LENGTH;
String errorMsg = null;
public DigitsOnlyTextField(int length) {
LENGTH = length;
addFocusListener(this);
}
public void focusGained(FocusEvent e) {
// empty
}
public void focusLost(FocusEvent e) {
if (e.isTemporary()) {
return;
}
String text = getText();
if (! isValid(text)) {
JOptionPane.showMessageDialog(this, errorMsg,
"Input Format Error",
JOptionPane.WARNING_MESSAGE);
}
}
public boolean isValid(Object obj) {
String text = (String) obj;
if (text.length() != LENGTH) {
errorMsg = "Text must be " + LENGTH + "characters long";
return false;
}
for (int n = 0; n < LENGTH; n++) {
if (! Character.isDigit(text.charAt(n))) {
errorMsg = "Text must be all digits";
return false;
}
}
return true;
}
public String getErrorMsg() {
return errorMsg;
}
}
Using this option means that the JTextField
just needs to
be replaced by DigitsOnlyTextField
.
Facade again
Initialising the listener
- The OK button listener must know the login use case controller
- It must know the login UI facade
- Pass these in by the constructor
package ui;
public LoginListener implements ActionListener {
LoginUseCaseController controller;
Login loginFacade;
public LoginListener(LoginUseCaseController c, Login login) {
controller = c;
loginFacade = login;
}
public void actionPerformed(ActionEvent e) {
int uid = loginFacade.getUid();
String passwd = loginFacade.getPasswd();
// now ask use case controller to login in the user
controller.login(uid, passwd);
}
}
Adapter pattern
- The listeners are triggered by events caused by user actions
- The listener methods have one parameter which is the event
- The listener must turn this into one or more system events
- The system event is used to call methods on domain objects
- This is an example of the adpater pattern:
Convert the interface of one class into another interface that
clients expect. Adapter lets classes work together that
couldn't otherwise because of incompatable interfaces
Changes in the business objects
- As a result of user activities, system events will be called
- Most of these will cause changes in the system state
- These changes can be communicated back to the user interface
layer in a variety of ways
- The system event can have a return value
boolean loginOk = controller.login(uid, passwd)
- The system may support queries
controller.login(uid, passwd);
if (controller.loginSuccessful())...
- The system may generate events that UI components listen for
PropertyChange events
- The delegation event model gives observers/listeners for changes
in GUI objects
- The
PropertyChangeEvent
is used for observers of
changes in general objects
- The constructor is
PropertyChangeEvent(Object source, String propertyName,
Object oldValue, Object newValue)
- e.g. whenever a SaleLineItem is added to a Sale, the total
changes. This can be sent to a listener using an event
new PropertyChangeEvent(sale, "totalChanged",
oldTotal, newTotal)
- This class has methods such as
Object getNewValue();
Object getOldValue();
- A
PropertyChangeListener
will get called by
the method
propertyChange(PropertyChangeEvent e)
PropertyChange list management
- A listener object will need to be added to a list of
listeners kept by the changing object
- The class
PropertyChangeSupport
can be used for
this
class PropertyChangeSupport {
addPropertyChangeListener(PropertyChangeListener);
firePropertyChange(PropertyChange);
}
- e.g. in
Sale
public class Sale {
private PropertyChangeSupport pcSupport =
new PropertyChangeSupport(this);
public void addItem(SaleLineItem item) {
Integer oldTotal = getTotal();
// add item
...
Integer newTotal = getTotal();
PropertyChangeEvent event =
new PropertyChangeEvent(this,
"totalChanged",
oldTotal, newTotal);
pcSupport.firePropertyChange(event);
}
public void addPropertyChangeListener(PropertyChangeListener lst) {
pcSupport.addPropertyChangeListener(lst);
}
}
- A
JLabel
can be made a listener by
public class CurrentTotalLabel extends JLabel {
void propertyChange(PropertyChangeEvent e) {
Integer newPrice = (Integer) e.getNewValue();
String newLabel = newPrice.toString();
this.setLabel(newLabel);
}
public CurrentTotalLabel(String text) {
super(text);
}
}
- To avoid the labels having to know the Sale objects, use something
like the POST as a facade again:
- Register the label as a listener to the POST
- Register the POST as a listener to the Sale
- When the Sale changes state, it posts an event to the POST
- When the POST receives an event, it posts it to it's
list of listeners
- The POST can have multiple event lists if needed,
for different types of events
Jan Newmarch (http://pandonia.canberra.edu.au)
jan@ise.canberra.edu.au
Last modified: Mon Oct 23 14:56:47 EST 2000
Copyright ©Jan Newmarch