Feedback


Part of creating a good user experience is ensuring that you provide plenty of feedback. Sun has already built a lot of feedback into Swing. For example, when you click the mouse button atop a JButton, the JButton repaints to appear as if it were physically depressed. This kind of information reassures the user about his or her actions.

The Sis application lacks a pertinent piece of feedback: When the user enters one of the filtered or formatted text fields, how does he or she know what they're expected to type? The user will eventually discover the constraints they are under. But he or she will waste some time as they undergo guesswork, trial, and error.

You can instead provide your users with helpful information ahead of time. Several options exist:

  • Put helpful information in the label for the field. Generally, though, there is not enough screen real estate to do so.

  • Provide a separate online help window that describes how the application works.

  • As the user moves the mouse over fields, display a small pop-up rectangle with relevant information. This is known as hover help, or tool tips. All modern applications such as Internet Explorer provide tool tips as you hover your mouse over toolbar buttons.

  • As the user moves the mouse over fields, display relevant information in a status bar at the bottom of the window.

For this exercise, you will choose the last option and create a status bar.

Unfortunately, for mouse-based testing, you must usually render (display) the user interface in order to test it. The reason is that components do not have established sizes until they are rendered. You can again use the Swing robot to help you write your tests. Note that the setUp in this test uses a couple of Swing utility methods that I will display in subsequent listings.

 package sis.ui; import junit.framework.*; import javax.swing.*; import java.awt.*; import sis.util.*; public class StatusBarTest extends TestCase {    private JTextField field1;    private JTextField field2;    private StatusBar statusBar;    private JFrame frame;    protected void setUp() {       field1 = new JTextField(10);       field2 = new JTextField(10);       statusBar = new StatusBar();       JPanel panel = SwingUtil.createPanel(field1, field2, statusBar);       frame = SwingUtil.createFrame(panel);    }    protected void tearDown() {       frame.dispose();    }    public void testMouseover() throws Exception {       final String text1 = "text1";       final String text2 = "text2";       statusBar.setInfo(field1, text1);       statusBar.setInfo(field2, text2);       Robot robot = new Robot();       Point field1Location = field1.getLocationOnScreen();       robot.mouseMove(field1Location.x - 1, field1Location.y - 1);       assertEquals("", statusBar.getText().trim());       robot.mouseMove(field1Location.x + 1, field1Location.y + 1);       assertEquals(text1, statusBar.getText());       Point field2Location = field2.getLocationOnScreen();       robot.mouseMove(field2Location.x + 1, field2Location.y + 1);       assertEquals(text2, statusBar.getText());    } } 

Conceptually, providing status information is a common need for all of your application's windows. Instead of cluttering each window with additional code, you can encapsulate the status concept in a separate class.

StatusBar

A StatusBar is a JLabel with additional functionality. You can associate an information String with each text field by sending setInfo to a Status object.

The test extracts the location of the first field, then moves the mouse to an arbitrary position outside this field. The status bar should show nothing; the first assertion proves that. The test then moves the mouse over the field, then ensures that the status bar contains the expected text. The test finally asserts that the status bar text changes to TEXT2 when the mouse is over the second field.

The SwingUtil class extracts common code used to create a simple panel and a frame for testing:

 package sis.util; import javax.swing.*; public class SwingUtil {    public static JPanel createPanel(JComponent... components) {       JPanel panel = new JPanel();       for (JComponent component: components)          panel.add(component);       return panel;    }    public static JFrame createFrame(JPanel panel) {       JFrame frame = new JFrame();       frame.getContentPane().add(panel);       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       frame.setSize(300, 300);       frame.setVisible(true);       return frame;    } } 

In StatusBar, the job of setInfo is to add a mouse listener to the text field. The listener reacts to mouse entry and mouse exit events. When a user moves the mouse atop the text, listener code displays the associated information. When a user moves the mouse away from the text field, listener code clears the status bar.

 package sis.ui; import java.awt.event.*; import java.util.*; import javax.swing.*; public class StatusBar extends JLabel {    private final static String EMPTY = " ";    public StatusBar() {       super(EMPTY);       setBorder(BorderFactory.createLoweredBevelBorder());    }    public void setInfo(final JTextField textField, final String text) {       textField.addMouseListener(          new MouseAdapter() {             public void mouseEntered(MouseEvent event) {                setText(text);             }             public void mouseExited(MouseEvent event) {                setText(EMPTY);             }          });    } } 

The test for StatusBar passes. Now you must attach the status bar to CoursesPanel. How will you test this? Your test for CoursesPanel could use the robot again. But it would be easier to ensure that each text field has been attached to the status bar.

Update the test in CoursesPanelTest. The assertion declares a new intent: A StatusBar object should be able to return the informational text for a given text field. It also suggests that the informational text should come from the field spec object, obtained from the field catalog.

 private void assertFields(String[] fieldNames) {    StatusBar statusBar =    (StatusBar)Util.getComponent(panel, StatusBar.NAME);    FieldCatalog catalog = new FieldCatalog();    for (String fieldName: fieldNames) {       JTextField field = panel.getField(fieldName);       Field fieldSpec = catalog.get(fieldName);       assertEquals(fieldSpec.getInfo(), statusBar.getInfo(field));       assertLabelText(fieldSpec.getLabelName(), fieldSpec.getLabel());    } } 

This will not compile because you have not implemented getInfo. Note also that you will need to associate a component name with the status bar. Here are the changes to StatusBarTest and StatusBar:

 // StatusBarTest ... public void testInfo() {    statusBar.setInfo(field1, "a"); assertEquals("a", statusBar.getInfo(field1)); } ... // StatusBar package sis.ui; import java.awt.event.*; import java.util.*; import javax.swing.*; public class StatusBar extends JLabel {    public static final String NAME = "StatusBar";    private final static String EMPTY = " ";    private Map<JTextField,String> infos =       new IdentityHashMap<JTextField,String>();    public StatusBar() {       super(EMPTY);       setName(NAME);       setBorder(BorderFactory.createLoweredBevelBorder());    }    public String getInfo(JTextField textField) {       return infos.get(textField);    }    public void setInfo(final JTextField textField, String text) {       infos.put(textField, text);       textField.addMouseListener(          new MouseAdapter() {             public void mouseEntered(MouseEvent event) {                setText(getInfo(textField));             }             public void mouseExited(MouseEvent event) {                setText(EMPTY);             }          });    } } 

You must also add a field, getter, and setter to Field. You'll need to modify FieldCatalog to populate each field with a pertinent informational string:

 ... static final String DEPARTMENT_FIELD_INFO =    "Enter a 4-character department designation."; static final String NUMBER_FIELD_INFO =     "The department number should be 3 digits."; static final String EFFECTIVE_DATE_FIELD_INFO =     "Effective date should be in mm/dd/yy format."; ... private void loadFields() {    fields = new HashMap<String,Field>();    Field fieldSpec = new Field(DEPARTMENT_FIELD_NAME);    ...    fieldSpec.setInfo(DEPARTMENT_FIELD_INFO);    put(fieldSpec);    fieldSpec = new Field(NUMBER_FIELD_NAME);    ...    fieldSpec.setInfo(NUMBER_FIELD_INFO);    put(fieldSpec);    fieldSpec = new Field(EFFECTIVE_DATE_FIELD_NAME);    ...    fieldSpec.setInfo(EFFECTIVE_DATE_FIELD_INFO);    put(fieldSpec); } 

Finally, the code in CoursesPanel to make it all work for the student information system:

 private Status status; private void createLayout() {    ...    add(coursesScroll, BorderLayout.CENTER);    add(createBottomPanel(), BorderLayout.SOUTH); } JPanel createBottomPanel() {    JLabel statusBar = new JLabel(" ");    statusBar.setBorder(BorderFactory.createLoweredBevelBorder());    status = new Status(statusBar);    JPanel panel = new JPanel();    panel.setLayout(new BorderLayout());    panel.add(statusBar, BorderLayout.SOUTH);    panel.add(createInputPanel(), BorderLayout.CENTER);    return panel; } JPanel createFieldsPanel() {    GridBagLayout layout = new GridBagLayout();    JPanel panel = new JPanel(layout);    int i = 0;    FieldCatalog catalog = new FieldCatalog();    for (String fieldName: getFieldNames()) {       Field fieldSpec = catalog.get(fieldName);       JTextField textField = TextFieldFactory.create(fieldSpec);       status.addText(textField, fieldSpec.getLabel());       addField(panel, layout, i++,                createLabel(fieldSpec),                textField);    }    return panel; } 

If you execute sis.ui.Sis as a stand-alone application, you should now see something like Figure 2. I rested the mouse pointer above the department field when I captured this screen image.

Figure 2. The Completed Look




Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net