Responsiveness


In the Sis method addCourse, insert a deliberate wait of three seconds. This wait might emulate the time required to verify and insert the Course object into the database.

 private void addCourse() {    Course course =       new Course(          panel.getText(FieldCatalog.DEPARTMENT_FIELD_NAME),          panel.getText(FieldCatalog.NUMBER_FIELD_NAME)); try {Thread.sleep(3000); }catch (InterruptedException e) {}    JFormattedTextField effectiveDateField =       (JFormattedTextField)panel.getField(          FieldCatalog.EFFECTIVE_DATE_FIELD_NAME);    Date date = (Date)effectiveDateField.getValue();    course.setEffectiveDate(date);    panel.addCourse(course); } 

Execute the application. Enter a department and course number and press Add. You should experience a 3-second delay. During that time, you cannot do anything else with the user interface! For an end user, this is a frustrating experience, since there is no feedback about why the application is not responding.

As an application developer, you can do two things with respect to responsiveness: First, provide feedback to the user that they should wait patiently for a short period. Second, ensure that the user is able to do other things while waiting.

Feedback comes in the form of a "wait" cursor. Windows represents a wait cursor using an hourglass. Some Unix desktops represent a wait cursor using a clock. You should provide an hourglass for any operation that does not immediately return control to the user.

 private void addCourse() {    frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));    try {       Course course =          new Course(             panel.getText(FieldCatalog.DEPARTMENT_FIELD_NAME),             panel.getText(FieldCatalog.NUMBER_FIELD_NAME)); try {Thread.sleep(3000); }catch (InterruptedException e) {}       JFormattedTextField effectiveDateField =          (JFormattedTextField)panel.getField(             FieldCatalog.EFFECTIVE_DATE_FIELD_NAME);       Date date = (Date)effectiveDateField.getValue();       course.setEffectiveDate(date);       panel.addCourse(course);    }    finally {       frame.setCursor(Cursor.getDefaultCursor());    } } 

The finally block is essential! Otherwise, any unnoticed or abnormal return from addCourse will burden the user with an hourglass as a pointer.

Providing a wait cursor is an adequate and necessary response for any prolonged wait period. But the real solution is to ensure that the user does not have to wait. The threshold is a half a second: You should spawn a slow operation off in another thread if it is going to take any longer. In the example shown here, I've separated the behind-the-scenes operation of adding the course from the code that updates the user interface. I've also disabled the Add button until the thread completes.

 // THIS IS AN INADEQUATE SOLUTION! private void addCourse() {    Thread thread = new Thread() {       public void run() {          panel.setEnabled(CoursesPanel.ADD_BUTTON_NAME, false);          Course course = basicAddCourse();          panel.addCourse(course);          panel.setEnabled(CoursesPanel.ADD_BUTTON_NAME, true);       }    };    thread.start(); } private Course basicAddCourse() { try { Thread.sleep(3000); } catch (InterruptedException e) {}    Course course =       new Course(          panel.getText(FieldCatalog.DEPARTMENT_FIELD_NAME),          panel.getText(FieldCatalog.NUMBER_FIELD_NAME));    JFormattedTextField effectiveDateField =       (JFormattedTextField)panel.getField(          FieldCatalog.EFFECTIVE_DATE_FIELD_NAME);    Date date = (Date)effectiveDateField.getValue();    course.setEffectiveDate(date);    return course; } 

A subtle but real problem exists with this code. It is not thread-safe! Since Swing uses a separate thread known as an event dispatch thread, it is possible for a user to click the Add button before the panel is completely updated. The user might see unexpected results.

You can rectify this by executing statements to update the user interface in the event dispatch thread. The class javax.swing.SwingUtilities contains two methods, invokeLater and invokeAndWait, to allow this. Each method takes a Runnable object that defines the code to execute in the event dispatch thread. You use invokeLater when you can allow the run method to execute asynchronously (when you don't need to "hold up" activities on the user interface). In our example, you want to use invokeAndWait, which results in the run method being executed synchronously.

Here's how addCourse might look using invokeAndWait:

 private void addCourse() {    Thread thread = new Thread() {       public void run() {          panel.setEnabled(CoursesPanel.ADD_BUTTON_NAME, false);          try {             final Course course = basicAddCourse();             SwingUtilities.invokeAndWait(new Runnable() {                public void run() {                   panel.addCourse(course);                   panel.setEnabled(CoursesPanel.ADD_BUTTON_NAME, true);                }             });          }          catch (Exception e) {}       }    };    thread.start(); } 

The big downside is that this change will break the SisTest method testAddCourse. The test presumes that clicking on Add will block until the course has been added to the panel. As a quick fix, you can have the test wait until elements appear in the panel's table.

 public void testAddCourse() {    ...    JButton button = panel.getButton(CoursesPanel.ADD_BUTTON_NAME);    button.doClick();    while (panel.getCourseCount() == 0)       ;    Course course = panel.getCourse(0);    assertEquals("MATH", course.getDepartment());    assertEquals("300", course.getNumber());    TestUtil.assertDateEquals(2006, 3, 17, course.getEffectiveDate()); } 

The change requires a small addition to CoursesPanel:

 int getCourseCount() {    return coursesTableModel.getRowCount(); } 



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