How often do you find yourself copying and pasting old code when developing
GUIs that are similar to others you've developed? Many times I find that if
I do that more than once or twice on some code, it's worthwhile to make a
component out of it for the benefits of reuse and highly maintainable code.
The initial overhead for creating a component is high, but it will pay
off if you design it well, making it useful for other occasions. The better
you design it, the more likely you'll use it in the future and the more
benefits you'll get.
ListPanel and ListTabPanel are two reusable components I developed. In
this article, I describe how you can use them to simplify and speed up your
GUI development. I also explain how they're implemented and hope you can
learn some useful skills from them.
ListPanel
ListPanel is a GUI component that allows a user to filter, sort,
display, and search a list of any object in different ways. Figure 1 shows a
list of Auction items that can be filtered by their status (open, closed).
The list can sort, display, and search by the Auction item number or name.
Using this component the developer writes only a few lines of code and some
inner classes to create the list GUI, which normally involves complicated
code. It's easy to understand and the code is very maintainable.
Figure 1
Please note that for simplicity, this component assumes that the way you
display and search data is the same way you sort data. You can modify this
component to make it more sophisticated and to handle the sort, display, and
search differently.
Creating an Instance of ListPanel
The following shows how easy it is to create the GUI in Figure 1 using
the ListPanel.
The ListPanel contains a list of Auction objects (see Listing 1 for the
Auction class). (Listings
17 can be downloaded from below.)
ListPanel listPane = new ListPanel();
listPane.setData(createTestData ());
listPane.addFilterSpec(new StatusFilter("Open") );
listPane.addFilterSpec(new StatusFilter("Closed") );
listPane.addSortSpec(new IDSort ());
listPane.addSortSpec(new NameSort ());
listPane.refreshGUI();
This code involves only five steps:
- Create the ListPanel with the constructor.
- Set the data (in this case, a list of all Auction objects) by setData.
- For each FilterSpec, call addFilterSpec.
- For each SortSpec, call addSortSpec.
- Call refreshGUI once you've set up all the filter and sort conditions.
Creating a FilterSpec
To create a FilterSpec, extend the abstract class FilterSpec (see
Listing 2) and provide implementation for the following abstract methods:
String getName(): Defines how the filter name is displayed in the
JComboBox
boolean evaluate(Object o): Defines the filter condition
To create the filter based on the Auction status use the following:
class StatusFilter extends FilterSpec
{
private String status;
public StatusFilter (String s)
{
status = s;
}
public boolean evaluate(Object o)
{
return (((Auction)
o).status.equals(status));
}
public String getName()
{
return status;
}
}
Creating a SortSpec
To create a SortSpec, extend the abstract class SortSpec (see Listing 2)
and provide implementation for the following abstract methods:
String getName(): Defines how the sort name is displayed in the JComboBox
getSortString(Object o): Defines how to sort, display, and search by
The code for creating the sort by ItemNo, Name of the Auction is as
follows.
class IDSort extends SortSpec
{
public String
getSortString(Object o)
{
return ((Auction) o).itemNo;
}
public String getName()
{
return ("Item No");
}
}
class NameSort extends SortSpec
{
publicString
getSortString(Object o)
{
return ((Auction) o).name;
}
public String getName()
{
return ("Name");
}
}
Listing 1 provides the code to generate the AuctionList.
Another Example
The ListPanel is a flexible component. Figure 2 shows another list
created on a totally different object, User. This list has three filters
(active, inactive, all) and two sorts (ID, LastnameFirstname). This entire
GUI is created in one routine, createListPane() shown in Listing 3.
Figure 2
In Listing 3, you can see that because the FilterSpec and SortSpec are
all small classes, I've made them all inner classes. In situations where I
have a lot of little classes used in one place, inner classes are useful to
avoid proliferation of classes.
ListTabPanel
ListTabPanel contains a ListPanel and a TabPanel. It provides a
structure that you can add tabs to to perform different actions on a
particular selected object from the list. The implementation uses the
Mediator pattern, where the ListTabPanel is a mediator that takes care of
the communication between the ListPanel and the TabPanel.
Using the same Auction class I discussed earlier and the AuctionList, I
create the AuctionPanel shown in Figure 3.
Figure 3
Creating the AuctionPanel involves three simple steps:
- Create the Auction ListPanel defined in the previous section.
- Create a TabPanel that contains three different tabs.
- Call the constructor, passing it the ListPanel and the TabPanel.
ListPanel l = AuctionList.createListPanel();
TabPanel t = createTabPanel();
ListTabPanel p = new ListTabPanel (l, t);
The details for creating the TabPanel in createTabPanel follows:
TabPanel t = new TabPanel();
t.addTab(new AuctionTab());
t.addTab(new MinimalTab("Seller"));
t.addTab(new
MinimalTab("Buyer"));
Here I create the TabPanel, and then add as many TabObjects as needed by
calling addTab. Each TabObject defines how the object is displayed and
handled in a particular tab. Let's look at how the first tab is implemented
in AuctionTab.
public class AuctionTab extends TabObject
{
public AuctionTab()
{
// ... Create the GUI here
}
public void setData(Object o)
{
// ... populate the data to the GUI
}
}
The AuctionTab extends TabObject, which is a JPanel. The developer
usually does three things to implement this tab:
1. Creates the GUI in the constructor
2. Implements the method setData to populate the GUI with an object
3. Manipulates the object in its own way and updates it as appropriate
(e.g., save to database, etc.); when manipulated, an object should call the
appropriate methods notifyAdd(), notifyDelete, or notifyUpdate() to let its
listener know of the change
Listing 4 provides the code for AuctionPanel.
Implementation of the ListPanel
The ListPanel component provides a GUI that can filter, sort, display,
and search any kind of object. The component has no idea what kind of
objects are in the list and how it should filter or sort; it's up to the
developer to define this information by calling:
- setData (List data)
- addFilterSpec (FilterSpec f)
- addSortSpec (SortSpec s)
With this information from the developer, the component then handles the
rest, which includes:
- Creating JComboBox to allow the user to choose the methods to sort and
filter
- Filtering the data according to the filter the user selected
- Sorting the data according to the sort the user selected
- Displaying the data according to the sort the user selected
- Implementing the search according to the sort the user selected
Jakarta Common Collection
To implement the filter and search mechanism, I need some methods where
I can:
- Filter from a list based on some condition and return a filtered list
- Search from a list based on some condition and return the first object
that matches
While the Collections class in the java.util package does provide a
search method, binarySearch(List l, Object o, Comparator c), there's no
mechanism to filter the data in the list. Furthermore, this search method
requires that I sort the list before passing the list in. In addition, if I
want the filter condition to be a union or intersection of two conditions,
the Collections class doesn't have set theory methods that I can apply to the
collections.
The Jakarta Commons Collections
(http://jakarta.apache.org/commons/index.html) is a set of
reusable components related to collections. It strives to provide some features that
were left unfilled by Sun's implementations in the Collection class.
Using Jakarta Commons Collections you can search and filter a collection
of data based on a Predicate using the method search and find in the
CollectionsUtil class. Predicate is an abstract class with a method
evaluate(Object o) that performs some Predicate that returns true or false
based on the input object. In addition, the CollectionUtils class provides set theory methods like intersection and union if you want to operate the filter on more than one condition.
Implementation Details
The ListPanel has three pieces of information after the developer
creates an instance of it: dataList (a list of all objects), filterList (a
list of all filters), and sortList (a list of all sorts).
Using filterList and sortList, ListPanel creates combo boxes that
contain lists of all available filters and sorts. When the user selects a
sort or a filter, the currentFilter or currentSort object is updated, and
the method refresh is called. In refresh, the data is first filtered as
follows:
// Filter the data according to currentFilter
filteredData = CollectionUtils.select(data, currentFilter);
displayModel.clear();
Iterator i = filteredData.iterator();
while (i.hasNext())
displayModel.addElement(i.next());
I get the filteredData by calling the select (List l, Predicate p)
method in the CollectionUtils class of Apache Common Collections. You should
now see why FilterSpec implements Predicate, because now all I need to do is
to pass currentFilter (which is a FilterSpec) to the select (List l,
Predicate p) method.
After the data is filtered, it's sorted as follows:
// Sort the data according to currentSort
displayModel.sort(currentSort);
displayList.setModel (displayModel);
// Display the data according to currentSort
displayList.setCellRenderer(currentSort);
I created a class SortableListModel (see Listing 5) that can sort data
by calling sort (Comparable c). The displayModel you saw earlier is a
SortableListModel, so I sort this list by simply calling displayModel.sort
(currentSort). You should now see why SortSpec implements Comparator,
because with that, all you need is to pass currentSort to this sort method.
Next, I display the data in the JList by setting the cellRenderer. Again
you can see that SortSpec extends DefaultListCellRenderer, which means you can just pass currentSort to the setCellRenderer(ListCellRenderer) method.
Last, when the user types something in the text field and hits the
search button, the method search does the following:
currentSort.setSearch(searchTF.getText());
Object o = CollectionUtils.find
(filteredData,currentSort);
displayList.setSelectedValue(o, true);
Here I first set the text to be searched, then use the
CollectionUtils.find (List list, Predicate predicate) method to find the
first object that satisfies the Predicate from the list. Again, you see that
SortSpec implements Predicate, which is why you can just pass currentSort to
the CollectionsUtils.find method. Once the object is found, select it on the
JList.
Listing 5 provides the code for the ListPanel.
Implementation of ListTabPanel
To understand how the ListTabPanel is implemented, you first need to
understand the Mediator pattern. Mediator is a pattern that promotes loose
coupling between classes. It accomplishes this by being the only class with
detailed knowledge of the methods of other classes. Classes inform the
Mediator when changes occur, and the Mediator then passes them on to any
other classes that need to be informed.
The ListTabPanel (which contains the ListPanel and the TabPanel) is the
Mediator here. The ListPanel and TabPanel don't know about each other. They
notify only the ListTabPanel of changes in themselves, or get notified by
the ListTabPanel when changes come in. Clearly defining the tasks of each
component will make coding easy and clear. Let's now look at the tasks of
each component:
Tasks of the ListPanel
Provide a list that can filter, sort, display, and search in different
ways.
When a user selects an item, it should notify its listener (in this
case the Mediator).
Provide update, delete, and add methods so another object (in this case
the Mediator) can adjust its list data.
Tasks of the TabPanel
Provide a setData method for each tab so that another object (in this
case the Mediator) can set data and populate the tab accordingly.
When a tab calls update, delete, or add on an object, it should notify
its listener (in this case the Mediator).
Tasks of the ListTabPanel
When an item is selected from the ListPanel, this component (as a
Mediator) gets notified and calls setData on the TabPanel.
When the TabPanel updates some object, this component (as a Mediator)
gets notified and calls update, add, or delete on the ListPanel.
Using PropertyListener
In my implementation, the communication between the ListPanel and
TabPanel and their Mediator is done by PropertyListener. The ListTabPanel is
a PropertyChangeListener. When you construct a ListTabPanel with a ListPanel
and a TabPanel, the ListTabPanel will add itself as a PropertyChangeListener
to the ListPanel and TabPanel.
tabPane.addPropertyChangeListener(this);
listPane.addPropertyChangeListener(this);
By doing so, when the TabPanel or ListPanel wants to notify the
ListTabPanel of anything, it'll call firePropertyChange to pass the property
change and the object that was changed to the ListTabPanel.
The ListTabPanel received these change events in its propertyChange
method, and it will do the appropriate thing to notify other components if
necessary.
public void propertyChange
(PropertyChangeEvent evt)
{
// Get notify when ListPanel
select some object
if (evt.getPropertyName().equals
(LIST_SELECTED))
// Ask TabPanel to set the
object to its screen
tabPane.getSelected().setData
(evt.getNewValue());
// Get notify when TabPanel update
some object
if (evt.getPropertyName().equals
(TAB_OBJECT_UPDATE))
// Ask ListPanel to update its list
listPane.update(evt.getOldValue(),
evt.getOldValue());
// .... Other events
}
The full source of TabPanel, TabObject, can be found in Listing 6, and
the full source of ListTabPanel can be found in Listing 7.
Conclusion
The ListPanel and ListTabPanel are two useful GUI components that are
highly reusable and can promote rapid GUI development. The ListPanel takes
care of the common features like sort, filter, search, and display on a
list. The ListTabPanel takes care of the interaction between a list of
objects and the detail panels on a specific object in that list. These two
components are created so that they're applicable on any kind of object and
provide a clean interface for developers. Using them, developers can
concentrate on coding the details specific to their object.
More important, this article not only described how to use these
components, but also their implementation. It's useful to understand the
whole design process for making a component reusable and loosely coupled,
resulting in clear and maintainable code.
Author Bio
Teresa Lau has been an independent Java consultant for over four years, with
an emphasis on financial applications. She received her MS in computer
science from the University of Waterloo, and her BS in engineering from the
University of California, Berkeley.
ttylau@whsys.com
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail: info@sys-con.com
Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.
|