Object-Oriented Widgets

A WHILE AGO I had a problem with a program I was writing. I was working on a dialog for a simple database application with contact information. The dialog was to display a List of names from the database, and when a user clicked on one of the names, detailed information about the person would be shown. I was using the AWT List class, and the more I thought about the problem, the harder it got. When an item got selected, the List class would report the first and last name of the person. In that case, my program would not receive the unique ID of the person—and to retrieve it, I'd have to somehow parse out the user's name and do a query. In order to make the parsing and query easier, I might have to display the names in the List like "LastName, FirstName," instead of the way I had originally wanted the user to see them: "FirstName LastName." And, what would happen if two users had the same name? The quick solution that first came to mind was to maintain a parallel Vector of Person objects or Person ID numbers. The program would add a Person object to this Vector every time it put his/her name in the List. Then, when a name was selected, I could fish out the real information from the Vector. But this would get messy fast. I would have to make sure that every piece of code that made changes to the List would also make changes to this parallel Vector. I also didn't like this solution because I'd seen the results of similar hacks done in very un-object-oriented (OO) C programming.

The true problem was that although the Java class libraries are OO, they only partially support OO programming. The AWT List widget only works with strings, so it's difficult to use it to view and select objects. The Swing JTable class can also be difficult to use. The information you want to display has to be somehow put into a TableModel and then plugged into a JTable. Programming in a truly OO style may mean implementing several different TableModels that extract the information you want to show. Put another way, the problem is similar to the "impedance mismatch" that occurs when relational databases are used with OO systems; only part of a program works easily and natively with objects, forcing them to be "flattened out" into scalar values, and then later "unflattened" back into true objects.

After realizing what my real problem was, I considered how I'd like to use widgets and objects. Here's what I came up with: It would be cool if I could take a bunch of objects and throw them to a widget, which would then display them in the best way it could. The widget would let the user select one or more of the objects. If the program asked the widget for the thing that got selected, it would get a reference to a real object, not just a description of it. An advanced widget would even let the user manipulate or edit the objects in some way.

I solved several user interface (UI) problems by extending an AWT and a Swing widget to make them act more OO. The work I describe here can easily be applied to other UI classes.

Extending the AWT List
The AWT List class has the following methods (among others) for adding and retrieving strings:
  • addItem(String)
  • getSelectedItem()
The AWT List class would be much more usable if it had a method like addObject(Object, String), which would add the given object to its List and represent it with the given String. It could also have a method addObject(Object) as a convenience that would call Object.toString() to obtain the String representation. Finally, it would have getSelectedObject(), parallel to the original getSelectedItem().

Implementing this was fairly easy: I just applied "standard" OO refactoring to my original hack. If I had kept on using the "parallel Vector" idea, I would have ended up with lots of applications, all using instances of List and all maintaining their own parallel Vectors. My solution was to have a special List class that maintains its own parallel Vector internally, eliminating redundant coding. The Vector would be private, and manipulated when a public method is called. Subclassing the List class was the best way to implement this. My new OOList class would support the full List API, allowing it to be used in IDEs or programs where the old List is. My new class would simply be a set of wrappers around the original methods, all very similar to this:
public synchronized void removeAll() (
    super.removeAll();
    objectList.removeAllElements();
);
Besides creating an OO interface and eliminating redundancy, this code is much safer. The internal Vector is guaranteed not to get out of sync with the List because clients can access it only through the public methods. This achieves a key goal of OO design: An object should guarantee the integrity of its own data. Listings 1 and 2 show two simple classes that represent typical domain objects. OOListTest.java in Listing 3 uses them to demonstrate how the new OOList class can be used. Figure 1 shows the results.


Figure 1. OOList

Extending the Swing JTable
Swing brought many improvements to Java. The JList class, in fact, allows any object type to be added to its list. This fixes the AWT List's String-only problem, when Swing can be used. There are still some areas of Swing, though, that can use improvement.

I soon found myself in a typical database programmer's situation: I was writing lots of applications where I needed to display information from a database, let the users change some of it, and then store the updates. I had already made an OO API for my data storage (which was relational), but making the UIs was turning into a lot of work. The programs were designed for a biological laboratory. Typically, a user would want to look at a set of 50 or so samples, each one having 10 properties such as temperature or quantity. The user could then edit some of the properties and save the modified objects.

The Swing JTable was the natural choice for displaying this data. The easiest way to store information in a JTable is to use the DefaultTableModel, which accepts a two-dimensional array, or a Vector of Vectors. JavaSoft doesn't recommend this approach, and sure enough, my needs were too complex for the simple DefaultTableModel class. When I realized this, I started to rewrite my code to use the JTable as JavaSoft intended. I made custom TableModel implementations that get plugged into a JTable. The easiest way to do this is to subclass AbstractTableModel and implement the following methods:
  • int getColumnCount()
  • int getRowCount()
  • Object getValueAt(int row, int col)
The JTable will then call these methods when it needs to. Optionally, other methods can be overridden in order to let the JTable change the table data, as well as display more detailed information. Here are some of the more interesting ones:
  • boolean isCellEditable(int row, int col)
  • void setValueAt(Object, int row, int col)
  • String getColumnName(int index)
  • Class getColumnClass(int index)
The table model would know about the particular properties of my domain objects, and let the JTable edit and view them. This worked better—my code was cleaner, but I soon ran into another problem. Every dialog I was writing needed to show a different set of objects, and for each type of object, I found myself making a new TableModel implementation.

It occurred to me that, once again, I was writing somewhat un-OO code in order to work with UI widgets: I had classes that dissembled my objects into scalar values, and reassembled them again. It was turning into a maintenance problem, because changing my domain classes (the actual classes I wanted to work with like "Sample") caused me to change the TableModels that know how to work with them. I started thinking about how I'd like to use JTables if I could. Instead of giving the JTable a two-dimensional array of scalar values, it would be better if I could give it a one-dimensional array of the objects themselves. Objects would be displayed one per row; and the columns would somehow automatically show the objects' properties. But how would this happen? I realized that the JavaBean system would be perfect. The new development process would look something like this:

First, a special TableModel, OOTableModel, is implemented. This class accepts a one-dimensional array of objects in its constructor. It analyzes the objects' class using the JavaBean Introspector to get a List of the class' PropertyDescriptors. Each PropertyDescriptor has information about a single JavaBean property, including:
  • A "display name" for the property.
  • A "read method" that gives access to the property.
  • Possibly a "write method" that sets a new value for the property.
Here's how the basic TableModel interface is implemented:
  • getRowCount()—Return the length of the object array.
  • getColumnCount()—Return the length of property List.
  • getColumnName(i)—Return the display name of property[i].
  • getValueAt(row, col)—Get the read method of property[col], and execute it on object[row].
  • setValueAt(object, row, col)—Similar to above, but get and execute the property's write method if there is one.
Finally, this last method gives a JTable information about whether a user should be allowed to edit a column:
  • isEditable(col)—Get property[i], and return true if it has a write method.
Once the OOTableModel was completed, I needed to make sure that my domain classes adhered to the JavaBean standard. This isn't hard to do: For any property abc, a class should provide access to it via methods named getAbc and setAbc, if it's writable. Alternatively, Beaninfo classes can be generated or written that give detailed information to the Introspector. Animal.java and Dog.java from the previous example are basic JavaBeans. Animal has three writable properties: "age," "name," and "healthy." Dog subclasses Animal and adds a read-only property, "ageInDogYears."


Figure 2. OOLTable

Finally, using the new classes is very easy. Listing 4 00JTableTest.java, gives an example. Figure 2 shows the results. Whenever I have a List of objects that I want to show the user, I instantiate a new OOTableModel with the object List, pass it to a JTable, and display it. The JTable automatically displays the objects one per line, and their properties one per column. The user can click on the cells and change the values. Properties that don't have a set method will be read-only and the JTable won't let the user edit them. It's a bit difficult to see in a screenshot, but the Dog's "ageInDogYears" property is read-only and no edit field appears when the user clicks on it. I also made a subclass of JTable called OOJTable as a convenience to save myself the step of separately instantiating the table model.

Notification
The JTable enhancement described so far is just the beginning of what can be done. One issue that isn't resolved yet is what happens if the objects change while they're being displayed. The TableModel and JTable classes have a nice notification protocol that's easy to use when subclassing AbstractTableModel like I have. The AbstractTableModel has several methods for telling a JTable that something has changed, such as:
  • fireTableDataChanged(...)
  • fireTableRowsUpdated(...)
  • fireTableCellUpdated(...)
The JTable can then query the TableModel for the new values and repaint the changed cells. Figure 3 shows the sequence of notification actions. A domain object lets the TableModel know that it has changed.


Figure 3. Notification.

Another part of the JavaBean specification describes "bound properties." A JavaBean automatically notifies Listeners whenever any of these properties are changed. Using bound properties is like using the Observer/Observable classes, except bound properties are more flexible because the Observable object isn't required to subclass a particular class. Instead, the PropertyChangeSupport class takes care of most of the work involved. Listing 5, Animal2.java, and Listing 6, Dog2.java, show the modified JavaBeans, which now have bound properties. All that remained was to change OOTableModel in two ways. First, it must try to register itself as a property listener of each object it receives. I did this by using reflection and ignoring any exceptions that are thrown. The TableModel must also be modified to react correctly when it's notified about a change. Its implementation of the propertyChange(PropertyChangeEvent) method acts like a relay, catching the property change notifications and, in turn, sending out notifications about TableModel changes.

With these additions, a JTable will automatically repaint itself when the data changes.

Benefits of OO Widgets
I discovered that being able to directly display real objects in UI widgets encourages better and more flexible designs:
  • References to many kinds of objects can be passed to the OOTableModel, including persistent database objects, domain objects, report objects, and remote RMI objects. Objects that are cast as abstract classes or interfaces can also be used.
  • The system uses standard JavaBean features, which means that most classes don't have to be modified to work with the OOTableModel. Third-party classes can be used without modification or access to their source code.
  • The need to spread information about domain objects throughout programs is reduced. Normally, a UI class must know something about the classes it displays. This introduces redundancy, which is a maintenance problem. With OO widgets, objects can be conveniently described just once where they should be—in the class source code. The system then reuses this description at runtime.
  • OO design and polymorphism can be used to create interesting effects such as database-like "views" onto the objects. Note the difference between the "Animals" table and the "Dogs" table in Figure 2.
  • OOTableModel and OOJTable are consistent with Swing design and can be combined with other Swing features like cell renderers.
Possible Applications
The most obvious use for these new widgets is in easy-to-construct dialogs. Very little extra coding needs to be done aside from checking whether an "OK" or "Cancel" button was clicked. The application can then proceed using the objects like it normally would. Moving beyond this, the notification/update feature has some interesting possibilities. A small system to monitor the state of a running program could be made. Some key objects in the system would be passed to OO widgets, where a user could observe changes that occur in them over the lifetime of the program.

All object references are considered equal and a remote reference can be displayed just as easily. This feature could be used in a distributed application control system. For example, a collection of references to remote servers could be passed to an OOJTable, allowing an administrator to view their status, shut them down, and start them up.

Ideas for the Future
A neat feature of the model-view design is that the pieces can be separated and used in other contexts. The OOTableModel doesn't have to be used with a JTable. Having table-like access to a set of objects and their properties can be helpful in other settings. For example, I've been playing with classes that use a TableModel to generate HTML in servlets. Coupling these with the OOTableModel has made it easy to quickly write Web applications. Taking this further, these HTML producing classes could create a layer between DHTML or HTTP that would change browser events into sets and gets to a TableModel.

An object browser could be created by extending the OO widget idea. For example, maybe clicking the right mouse button over a cell in a JTable would cause that object to be "inspected" in a new window. All of its properties would then be displayed and could be edited. These properties could then themselves be inspected, etc.

The OOTableModel class could be easily modified to accept a Vector or Enumeration in its constructor. This would make it easier to use in typical applications. In these cases, though, there wouldn't be any guarantees that all objects would be the same type, or could be displayed.

Changes in objects can be detected with the notification enhancements I describe. However, changes to the List of objects itself are not accounted for. This might be a handy feature: detecting additions and deletions from the List of objects, and notifying Listeners about them.

Finally, it would be interesting to enhance other Swing model interfaces. As I mentioned in the HTML example, a TableModel is a useful tool for abstraction apart from Swing. Having easy-to-use extensions added to the other Swing interfaces could enable new types of flexible applications to be built around them.

Featured

Most   Popular
Upcoming Events

AppTrends

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.