Fine-Tuning the User Interface


Fine-Tuning the User Interface

During the Elaboration phase we created a basic structure for the user interface and tested different ways of presenting the information. The user interface was "rough" at best. The menus were incomplete and many of the visual objects were not properly sized . At the end of the Elaboration phase, PSP Tools didn't feel like a finished product, nor was it supposed to.

In the Construction phase we needed to add new features and put some polish on the user interface. Version 1 still doesn't have the look and feel we hoped for, but it is completely functional and feels good enough for the first release. The following list identifies the major modifications and additions we made to the user interface during the Construction phase:

  • Menus with appropriate functions

  • Context menus on the tree items

  • Tables to show the time entries and defect details

  • User preferences

In the following sections we discuss each of these in more detail.

Dealing with Menus

Creating menus is easy. We showed the basic menu construction code in Chapter 7 in Listings 7.2 and 7.3. The tricky parts are separating the menu items into logical groups, giving the menus and menu items meaningful names , and making sure that the items are enabled or disabled at the appropriate times.

Naming and Grouping Menu Items

Determine the appropriate menu item groups for your application by following general standards and referring to your use cases.

Most applications have a File menu and a Help menu. These have standard items such as New and About , respectively. Use the accepted standards for your items. For example, to create a new project database with PSP Tools, you would probably look in the File menu because that's where most applications put similar functions. That's exactly where we put the New Database menu item (seeFigure 9.1).

Figure 9.1. Standard File menu items

graphics/09fig01.jpg

The menu items shown in Figure 9.1 exhibit another important feature for naming menu items. When clicking on an item causes a dialog box to open so that you can complete the operation (other than a login dialog or something of that type), place an ellipsis () after the item. This tells the user that more input is needed to complete the operation.

Notice that we chose to say New Database and Open Database rather than just New or Open . The additional information is especially important for the database creation command because the user will also create new tasks , new time entries, and new defect entries for a specific database. Make sure that the context of any operation is clear from the menu command. When in doubt, add modifying words.

We also use separators (horizontal lines) between groups of items in a menu. This is just a visual cue for the user. It helps us group those operations that we think are similar to each other.

When naming menu items, also refer to your use cases. Earlier in the project, you spent some time naming the use cases and the alternate flows. You can use these names to help determine what to call the menu items. For example, an alternate flow of events in the Record Personal Engineering Statistics use case is called Enter Actual Time. We could have created a menu item called just that Enter Actual Time . However, we provided two ways to enter the time information: the user can enter the exact data needed or let the system record the data automatically through a timer. We decided to create two entries called New Time Entry and Time Activity .

The first command displays a data entry dialog box. The second invokes the activity timer tool (discussed later in this chapter). Both these menu items are under the Project menu. After you open a project, all of the actions that affect the data for that project are found under the Project menu.

Enabling and Disabling the Menu Items

As with creating menus, enabling and disabling menu items is easy. The tricky part of getting it right, however, is to toggle the availability at appropriate times. For example, it makes no sense to let the user enter defect data for a task if a database is not open or no task has been selected.

You can quickly make your code overly complex if you try to keep the menu items synchronized with the state of the application. Chris used a neat technique that kept the necessary code in one file, and kept most of the real action in one method. Let's look at the method that enables and disables the menu items. Listing 9.1 shows the beginning of the setMenuFor() method in PSPMainFrame.java.

Listing 9.1 Enabling and disabling menus at the proper time
 /**  * Enables the proper menu items  *  @param  int menuType one of the defined values at the top of   * this file  */  public void  setMenuFor(  int  menuType) {  switch  (menuType) {  case  PSP_CLOSED:                fileExport.setEnabled(  false  );                projectNewTask.setEnabled(  false  );                projectDeleteTask.setEnabled(  false  );                projectNewTime.setEnabled(  false  );                projectNewDefect.setEnabled(  false  );                projectNewUser.setEnabled(  false  );                projectTimeActivity.setEnabled(  false  );                projectProperties.setEnabled(  false  );                toolsUserInfo.setEnabled(  false  );  break  ;  case  PSP_TIMESUMMARY:                fileExport.setEnabled(  true  );                projectNewTime.setEnabled(  true  );                projectNewUser.setEnabled(  true  );                projectTimeActivity.setEnabled(  true  );                projectProperties.setEnabled(  true  );                projectNewTask.setEnabled(  false  );                projectDeleteTask.setEnabled(  false  );                projectNewDefect.setEnabled(  false  );                toolsUserInfo.setEnabled(  true  );  break  ;           //... 

The method takes a value that specifies the state of the application as a named constant like PSP_CLOSED , which tells us that a database has been closed and there is no active, open database. The code is one switch statement. For each state, we turn the appropriate menu items on or off. Whenever we added a new menu item that required enabling and disabling based on the application state, we just added a line to each of the cases. Each of the objects referenced in the cases, such as fileExport , are JMenuItem objects.

For a complex application, you probably want to create a document that indicates the specific menu configurations for the different states. You can easily do this with a matrix; we prefer using a spreadsheet application for this purpose.

The PSPMainFrame.java file contains all of the menu handling code. Each action invokes a menu handler that calls the appropriate private method. Once the action has been completed, we call the setMenuFor() method to synchronize the menus. Listing 9.2 shows the doFileClose() method. After the database is closed, we call setMenuFor() with an argument value of PSP_CLOSED .

Listing 9.2 Synchronizing menus after closing a database
 /**  * Execute the Close command in the File menu.  */  private void  doFileClose() {      PSPDatabase db = PSPTools.getCurrentDatabase();  if  (db ==  null  ) {  return  ;          // should never happen      }  try  {           db.close();      }  catch  (PSPDatabaseException e) {           //TBD      }      PSPTools.setCurrentDatabase(  null  );      projectTree.setRootNode("No Project");      fileClose.setEnabled(  false  );      //select the root node      projectTree.setSelectionRow(0);      //disallow creation of new tasks      setMenuFor(PSP_CLOSED);      //blank the right side      setRightPane(  new  JPanel()); } 

Menu items also need to be synchronized when the user selects an item in the tree in the left panel. For example, if you select a task, you should be allowed to enter a defect or a time entry, or invoke the activity timer. If you select the root of the tree, it doesn't make sense to perform these actions because the application doesn't know what task to associate with the data. To handle this situation, we call the setMenuFor() method from appropriate places in PSPTree.java. This is shown in Listing 9.3.

Listing 9.3 Synchronizing menus after clicking on a tree item (excerpt)
  if  (nodeInfo  instanceof  Task) {      ((Task)nodeInfo).setTopTab(getRequestedTab());      JPanel newPanel = ((Task)nodeInfo).getPanel(nodeInfo);      ((PSPMainFrame)getTopLevelAncestor()).setRightPane(newPanel);      ((PSPMainFrame)getTopLevelAncestor()).setMenuFor(PSPMainFrame.PSP_TASK);      ItemID = ((Task)nodeInfo).getTaskID(); }  else if  (nodeInfo  instanceof  PSPTimeSummary) {  ... 

Perhaps in a future release we will find a way to synchronize the menus by making calls from just one place. Code refactoring will continue as long as we work on the project. The solution we have is good enough for our purposes. We can easily deal with two calling locations for this application.

Adding Context Menus

Most applications that provide a tree view of data allow you to perform some actions by right-clicking on an item and choosing an action from a pop-up menu. [1] We wanted PSP Tools to follow this convention when you right-clicked on a tree node, as shown in Figure 9.2. By design, the menu contents are the same as the Project menu contents. The Project menu items are enabled or disabled based on the type of item selected in the tree; the same rules apply to the items in the pop-up menu.

[1] Other names for pop-up menus are context menus and shortcut menus .

Figure 9.2. Pop-up menu for clicking on tree items

graphics/09fig02.jpg

We had a choice at this point. We could have made the pop-up menu show only those actions relevant to the item clicked, or we could duplicate the whole Project menu. We chose the latter approach for these reasons:

  • It was much easier to display the whole Project menu. To show only those actions relevant to a particular item, we would have had to create separate menus for each item type and populate the menus accordingly .

  • The code to create both menus was the same and made it less likely that the code would diverge if we made changes later.

Whenever the user right-clicks on a tree item, the code in Listing 9.4 is called. The showPopup() method gets the Project menu's contents and displays it in the pop-up menu as shown in Figure 9.2. The first two lines inside the condition select the item that was clicked. This is the expected behavior when you right-click an item. The remaining lines are responsible for displaying the menu.

Listing 9.4 Displaying the pop-up menu for tree items
 private void showPopup (MouseEvent me) {      if (me.isPopupTrigger()) {          //find and set the corresponding tree item          int index = getClosestRowForLocation(me.getX(), me.getY());          setSelectedIndex(index); JPopupMenu popup = ((PSPMainFrame)getTopLevelAncestor()).getProjectMenu().getPopupMenu();       popup.show (me.getComponent(), me.getX(), me.getY()); popup.setInvoker(((PSPMainFrame)getTopLevelAncestor()).getProjectMenu());           }      } }; 

Displaying Time and Defect Details

For any task, the PSP Tools user must be able to enter details about the time spent in each PSP phase and the defects uncovered. Entering the data is straightforward: we use input dialog boxes for both types of entry. We also provide a timing tool that automatically enters the time details.

Presenting the data required us to think about how the PSP Tools user would use the information. Some data is active . For example, task summary data continually reflects the current state of work on that task. Time and defect entries contribute to the task summary. Once you enter a time you aren't going to update it, unless you entered it incorrectly. Once the entries are correct, you will mostly want to scan the entries for a task, perhaps to detect a trend or anomaly in the data. The best way to display this type of data is in a table. Yet, you still want to allow users to change some of the fields. Providing this capability, as shown in Figure 9.3, isn't a trivial task.

Figure 9.3. Presenting details in a table

graphics/09fig03.jpg

When you present data in a table view, you have to decide which of the cells can be modified and which of the modifiable cells require data validation. For the time entries shown in Figure 9.3, any of the cells can be modified. The Date, Time, and Phase fields must be validated . The comments have no restrictions (but we did have to modify the database code to allow any characters to appear).

Java provides special classes for certain data types such as dates. To install a date field in the table you need to develop a table model. [2] The Java Swing framework provided a default table model that can be easily extended. If you want to extensively customize the table presentation, you may need to write your own table model. This isn't a trivial task, and it's one that we didn't want to tackle for this release.

[2] Many of the Swing classes use the Model-View-Controller pattern to separate data from its presentation and logic. Table and tree models are two such classes that we used in PSP Tools.

The PSP Tools table model is defined in the PSPTableModel.java file. There are just a few methods other than the constructor. Most of the methods set up the table headings and insert a row of data. We used the same class for both time and defect data tables. As PSP Tools matures, we will probably refactor this class into two separate ones.

One method that we require is the isCellEditable() method. We want to make sure that the user is prevented from changing specific fields through the table interface. The isCellEditable() method takes a row number and column number and returns true if the cell at that location is editable; otherwise it returns false .

To understand how the tables work, look at the PSPTimeSummary.java file after you download the source files from the book's Web site. This file contains most of the interesting methods that you need to think about when you implement a table. Let's look at a couple of the more complex methods.

The largest method is createInfoPanel() , which sets up the panel with the table. It goes through the following steps:

  1. Create a JPanel with a GridBagLayout as its layout. This is the most efficient way to create a table. This layout is more complex than a GridLayout but provides much more flexibility. If you aren't familiar with GridBagLayout , you might find the time to learn it well spent.

  2. Create a PSPTableModel and make it the model for this panel's table.

  3. Make this panel the TableListener for the table. This adds extra responsibility to the panel and may be refactored into a separate class in future releases.

  4. Install a JComboBox in the second column and make it the cell editor for that column. For time data, the second column represents the phase to which the time applies.

  5. Load the data into the table. Read the time data for the selected task from the database and put it into the table.

  6. Add the Update and Cancel buttons to the panel. These buttons are enabled once the user changes information in the table.

  7. Position the visual items in the panel with appropriate sizes.

When you work with tables, setting different types of data in the cells can be confusing. To put the JComboBox into the table's second column, we call the private method setComboBox() in Step 4 above. Listing 9.5 shows this method. The trick is to set the CellEditor object associated with a column to be the JComboBox . We create the combo box and populate it with the phase names from the database. Then we call setCellEditor() on the specified column's model. Yes, there are other models to work with, but in most cases the default models are sufficient.

Listing 9.5 Inserting a JComboBox in a table
  public void  setComboBox(  int  column){      TableColumn defectColumn =            jt.getColumnModel().getColumn(column);      JComboBox comboBox =  new  JComboBox();  try  {            PSPPhaseAccessor tP =            PSPTools.getCurrentDatabase().getPhaseAccessor();            Vector vPhases = (Vector)(tP.getPhases().clone());  for  (  int  i = 0; i < vPhases.size(); i++) {                PSPPhase phase = (PSPPhase)vPhases.get(i);                comboBox.addItem(phase.getPhaseName());                }            }  catch  (PSPDatabaseException e) {            System.out.println("Error getting Phases from DB");       }      defectColumn.setCellEditor(  new  DefaultCellEditor(comboBox)); } 

The final part of implementing the tables is to install a listener to monitor when the user makes changes to the data presented in the table. When the code detects a change it enables the Update and Cancel buttons. When a user clicks the Update button, the changed data is written back to the database.

The PSPDefectSummary.java class is similar to the PSPTimeSummary.java class. There are two combo boxes for the defects: one for the phase in which the defect was injected and one for the phase in which it was resolved.

Adding User Preferences

When you think about user preferences, you probably imagine settings that the user can customize directly. For example, many applications allow the user to set font size, color schemes, and other visible characteristics of an application. In the first release of our application, we allow users to set preferences indirectly only, for example, to set the size of the application's window.

The initial users of PSP Tools quickly became annoyed that every time they opened a database they had to navigate from their home directory. They wanted to start from the directory with the last database they used. They also had to continually resize the windows . As an annoyed early adopter, Chris decided to find a solution. He created a user preferences file that contains an XML (eXtended Markup Language) file describing the preferences. His implementation is a very simple one taken from The Java Cookbook by Ian Darwin.

The utility class, PSPUserPrefs.java , implements everything needed to read or write user preferences. The class has three types of methods:

  • The getter methods read values of the different characteristics from the file.

  • The setter methods write the values of the characteristics to the user preferences file.

  • The I/O methods perform all of the physical file access to and from the file.

For this release, there is one user preference file per computer. The file resides in the directory from which PSP Tools is launched. As a result, if multiple users are using PSP Tools on the same computer, they all have the same preferences. We discussed this with Russell. He didn't want to delay delivery, so he pushed the request for a single user preference file per user to a future release.

The implementation makes it very easy to add a new user preference. You write a getter method, as shown in Listing 9.6. You use helper methods to read and write string or integer values. Next you write a setter method, illustrated in Listing 9.7. Finally, you call the appropriate method whenever you need to access the value. Listing 9.8 shows how the user preferences are used to set the divider location for the split panel when we created the main window.

Listing 9.6 User preferences getter method
  static public int  getDividerLocation()  {  return  getIntValue("Divider"); } 
Listing 9.7 User preferences setter method
  static public void  setDividerLocation(  int  newDivider) {     setIntValue("Divider", newDivider); } 
Listing 9.8 Calling a user preferences method
 JSplitPane createSplitPane() {      splitPane =  new  JSplitPane(JSplitPane.HORIZONTAL_SPLIT);      splitPane.setContinuousLayout(  true  );  int  divider = PSPUserPrefs.getDividerLocation();  if  (divider == 0) {           splitPane.setDividerLocation(0.25);      }  else  {           splitPane.setDividerLocation(divider);      }      projectTree =  new  PSPTree();      leftPane =  new  JScrollPane(projectTree);      rightPane =  new  JScrollPane();      splitPane.setRightComponent(rightPane);      splitPane.setLeftComponent(leftPane);  return  splitPane; } 

Using XML as the format for the user preferences file means that we can view the file with a text editor. Because there is a consistent, simple format, we can quickly see the settings.

The implementation we used is almost trivial. Every time you get or set a user preference value you read or write the complete file. [3] This is good enough for what we need now and, if the day comes when we need to expand the user preference capabilities, we can extend the implementation to take advantage of the latest Java XML technology. We think this is a fine compromise of implementing the simplest thing that will work and looking ahead to the future.

[3] A user preferences file is typically smaller than 200 bytes.



Software Development for Small Teams. A RUP-Centric Approach
Software Development for Small Teams: A RUP-Centric Approach (The Addison-Wesley Object Technology Series)
ISBN: 0321199502
EAN: 2147483647
Year: 2003
Pages: 112

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