YOUR FEEDBACK
The 4 Core Principles of Agile Programming
Siegfried wrote: Actually, every elephant has two left feet, and two right...


2007 West
GOLD SPONSORS:
Active Endpoints
Your SOA Needs BPEL for Orchestration
BEA
Virtualized SOA: Adaptive Infrastructure for Demanding Applications
Nexaweb
Overcoming Bandwidth Challenges with Nexaweb
TIBCO
What is Service Virtualization?
SILVER SPONSORS:
WSO2
Using Web Services Technologies and FOSS Solutions
Click For 2007 East
Event Webcasts

2008 East
PLATINUM SPONSORS:
Appcelerator
Think Fast: Accelerate AJAX Development with Appcelerator
GOLD SPONSORS:
DreamFace Interactive
The Ultimate Framework for Creating Personalized Web 2.0 Mashups
ICEsoft
AJAX and Social Computing for the Enterprise
Kaazing
Enterprise Comet: Real–Time, Real–Time, or Real–Time Web 2.0?
Nexaweb
Now Playing: Desktop Apps in the Browser!
Sun
jMaki as an AJAX Mashup Framework
POWER PANELS:
The Business Value
of RIAs
What Lies Beyond AJAX?
KEYNOTES:
Douglas Crockford
Can We Fix the Web?
Anthony Franco
2008: The Year of the RIA
Click For 2007 Event Webcasts
SYS-CON.TV
TOP THREE LINKS YOU MUST CLICK ON


A GUI Painter Friendly Table Component
The principle of the column container

Digg This!

In the early days of Java, GUI forms were written, not drawn. They were created by writing code that instantiated components and added them to containers with various layout constraints. Then the program was run and the result could be admired. This way of working, WYGIWYG (what you get is what you get) was often quite fun, more often frustrating, and never very productive. Today we have a JavaBeans specification and integrated development environments (IDEs) with GUI painters. Some of these are doing really good jobs, considering the difficulties with layout managers and platform portability.

With most components, such as text fields and buttons, the principle of dropping them on the form, setting properties, and adding event listeners is quite sufficient. The JTable though is more problematic. It's just too complex to configure with simple property editors and also so common that you don't want to have to write a lot of code every time you use it.

You can drop a table in a JScrollPane and set a lot of properties on the JTable, but when it comes to adding and customizing columns, the GUI painter can't help you since the columns are not JavaBeans. One solution is for the GUI painter to provide an editor for the table model property, thereby letting you define columns and set a few attributes on them. However, I have never seen an editor that will allow you to customize the columns of the table with the same flexibility you have when you customize text fields on a form.

There is, however, a completely different way to go, which is the one I chose for the table component in our own class library DOI, called the DoiTable.

Design Time Behavior
The DoiTable doesn't have a table model property editor at all. In fact, when you drop it on the form it doesn't even look like a table. Instead, it behaves like a container during design time, and you fill it with columns by dropping DoiTableColumn components inside it. At runtime, though, it automatically converts itself to a JTable with all the column properties taken from the design time column components.

Figure 1 shows the design time look of a simple table with four columns. The screenshot is taken from the NetBeans form editor. When the designer drops a table on the form, it appears as a big rectangle. The designer can then give the table a label and activate tools for inserting and deleting rows by setting properties on the table. Note the "Table" label and small tool bar above the rectangle. The rectangle is the drop area for columns. During design time this area is an ordinary panel with a flow layout in which column components can be dropped and reordered. The columns must be instances of the DoiTableColumn class. If you accidentally drop some other type of component inside it, the drop area turns red.

A DoiTableColumn is a direct descendant of the DoiTextField class, which is the standard text field in the DOI library, overridden to change the design time appearance and add some properties and behavior that is specific to a table column. As you can see, I've tried to make the column components look a bit like the columns they will become at runtime. From the GUI painter's point of view, the table is just a container. Therefore, the painter will allow you to set properties on each individual column as if they were ordinary fields on a panel, which is exactly what they are, until you run the application. In Figure 1, one of the columns is selected so you can see the property sheet for it in the lower right pane. Note also that the column components retain their preferred size even if the table is too narrow to show them all on one line. The fourth column, "Logical", doesn't fit, so it's placed on a new row. This behavior is consistent with any other flow layout panel. Although I could have made them resize themselves to mimic the behavior of a JTable more closely, I decided against it to make the columns easier for the designer to work with.

This is basically how the table component presents itself to the designer. To the user, however, it looks just like a JTable in a JScrollPane, as shown in Figure 2. I'll shortly go into the details on how this conversion happens, but first a little bit about how the table component communicates with the GUI painter.

Adjusting the BeanInfo
Every JavaBean component that you can draw on a form must have a supporting BeanInfo object, which is an instance of a class that implements the java.beans.BeanInfo interface. The BeanInfo object is used by the GUI painter to determine which properties and events the bean has. Although it can be created automatically using introspection, it's usually written by the author of the bean. Writing such a class is outside the scope of this article, but there is one important feature that is often forgotten when BeanInfo classes are described: the "container delegate" property. At the time of writing it isn't even mentioned in the Java Tutorial. Without this property, all beans must fall into the following two categories:

  1. Component beans such as JTextField or JButton - you drop them in containers but you don't drop anything inside them.
  2. Simple container beans such as JPanel - they are initially empty and you can drop components inside them.
The GUI painter can tell them apart by treating empty containers as category 2 and all other beans as category 1. The DoiTable, however, falls into a third category. It isn't just a container, but a container that initially has a label, a tool bar, and an inner container for the columns. Without a special "trick" in the BeanInfo class, the GUI painter would think that the DoiTable is an ordinary component because it isn't empty and won't let you drop anything inside it. This is certainly not the behavior we want, so we must inform the GUI painter that it is a container and that it has a special place for dropping stuff. The following code excerpt from the DoiTableBeanInfo class shows how this is done:


 public BeanDescriptor getBeanDescriptor()
 {
  BeanDescriptor bd =
   new BeanDescriptor(itsBeanClass);

  bd.setName("DoiTable");
 bd.setValue("isContainer",
        Boolean.TRUE);
  bd.setValue("containerDelegate",
        "getColumnContainer");

  return bd;
 }

The method creates a BeanDescriptor, which is an object that contains basic properties about the bean. While some of these properties have dedicated methods such as setName, others are set using the generic setValue method. In the code above, the property isContainer is set to TRUE to tell the GUI painter that although this bean isn't empty, it is still a container. We also have to tell the GUI painter which method on our bean returns the inner container by setting the property containerDelegate to the name of the method. In the DoiTable case, the method is called getColumnContainer.

Converting to Runtime Behavior
When the application is run we obviously don't want the table to look like it does in the GUI painter. Instead we want the drop area, a.k.a. the column container, to convert itself to a real JTable. This conversion happens in the method addNotify, which is called automatically on every component when it is added to a displayable container. This method may be called several times, so we must make sure the table doesn't attempt to con-vert itself more than once. Also, we don't want it to convert itself at all when we are using the table in the GUI painter. To test for design time or runtime mode, there is a method in the java.beans.Beans class called isDesignTime. This method returns true when called from a com-ponent in a GUI painter, and false otherwise.

The first thing we need to do is implement the addNotify method:


 public void addNotify()
 {
  super.addNotify();
  commitColumnContainer();
 }

The first thing the method does is invoke the same method on the superclass to let it do whatever it needs to do, then it calls the method commitColumnContainer to do the real work. This method looks like:


 public void commitColumnContainer()
 {
  commitColumnContainer(false);
 }

As you can see, it doesn't do much; it just delegates to another method. The reason for this is that the other method has a parameter that allows the caller to force a conversion even if we are in design time. This is useful in certain circumstances, which I'll get back to later. For now we'll look at the first few lines of the "real" commitColumnContainer method:


 public void commitColumnContainer(
      boolean pForce)
 {
  if (!pForce && Beans.isDesignTime())
   return;
  if (itsColumnContainer == null)
   return;

The method starts by checking if a conversion should happen at all by testing the force parameter and calling the isDesignTime method. If these tests are passed, it goes on to check if the table has already been converted. The column container panel is created and added to the table by the constructor and removed when the conversion is completed. This means that if it is null, the table is already converted and the method returns immediately. Now the real conversion can be done. We start off by transferring all column beans from the column container into an internal array:


  int ccc =
   itsColumnContainer.getComponentCount();
  
  itsColumns = new DoiTableColumn[ccc];
  	for (int i = 0; i < ccc; ++i) {
   DoiTableColumn column =
    (DoiTableColumn)itsColumnContainer
     .getComponent(i)
   itsColumns[i] = column;
   column.setTable(this);
  }

Each column is given a reference back to the table using the setTable method of the DoiTableColumn class. This reference is used by the column to access various properties on the table that affect its behavior. Now it's time to get rid of the column container and replace it with a scroll pane:


  remove(itsColumnContainer);
  itsColumnContainer = null;
  itsScrollPane = new JScrollPane();
  add(itsScrollPane, BorderLayout.CENTER);

The scroll pane will eventually contain a JTable, but before we can create it we need a column model, the object used by Swing's JTable to represent its columns. A JTable can automatically create the column model based on its table model, but we don't want that because the DoiTableColumn objects contain much more information about the columns than is contained in an ordinary table model, e.g., preferred width in characters, resizability, label text. etc.

The below code creates a column model that contains column objects of Swing's TableColumn class, with relevant properties copied from the corresponding DoiTableColumn objects:


  TableColumnModel colmod =
   new DefaultTableColumnModel();

  for (int i = 0; i < ccc; ++i) {
   // Get the column bean. Skip if hidden.
   DoiTableColumn column = itsColumns[i];
   if (column.isHidden())
    continue;
   // Create a Swing column.
   TableColumn swingColumn =
    new TableColumn();
   // Copy properties.
   swingColumn.setHeaderValue(
    column.getLabelText();
   swingColumn.setResizable(
    column.isResizable();
   // Add to column model.
   colmod.addColumn(swingColumn);
  }

There is still one little detail before we can create the JTable. We need a table model. A JTable can't exist without a table model so we need to create one that is initially empty. This is accomplished with the following code:


  TableModel tm =
   new DefaultTableModel(0, ccc);
Now the JTable can be created and added to the scroll pane that has replaced the column container. We also tell it not to automatically create a new column model if the table model is replaced later:


  JTable jt = new JTable(tm, colmod);
  jt.setAutoCreateColumnsFromModel(false);

  itsScrollPane.add(jt);

That's it. The DoiTable bean now contains a JTable within a JScrollPane instead of a column container panel. The DoiTableColumn beans still exist though, and there is an implicit association between each column bean with the corresponding Swing TableColumn object in the column model. This association will prove very useful for later enhancements, some of which I'll hint at in the next section.

I promised to mention the purpose of the pForce parameter. This parameter can be used by subclasses of the DoiTable that create and add all columns. Let's say you want to create a bean called PhoneNumberTable, with a number type column and a phone number column. This bean would add its columns in the constructor and then call commitColumnCon-tainer(true) to force the conversion to a JTable. In this case, the force parameter is necessary since the conversion must happen in design time as well as runtime.

Enhancements
The purpose of this article is to show you the principle of the column container, not how to write a full-fledged table component. To do that, I'd probably have to fill 10 issues of JDJ. For this reason the code examples shown of what really happens inside the DoiTable have been simplified. Still, I'd like to round off with a brief list of some interesting features in the real DOI classes.

Runtime Propagation of Properties
Many of the DoiTableColumn properties are automatically propagated to the JTable when changed at runtime. This allows runtime code to dynamically change the table by simply setting properties on the DoiTableColumn bean, which is much easier than doing it through the JTable. For example, the column header is updated if the label text of the column is set. This propagation is accomplished through the implicit association between the invisible column bean and the visible table column.

Runtime Synchronization of Cell Values
The DoiTable has a property called ContextRowNo that can be set programmatically. It is also updated automatically when the user selects a row. I mentioned earlier that the DoiTableColumn class is a subclass of a class called DoiTextField, which is an enhancement of JTextField. This means that a DoiTableColumn bean can have a value. The context row is used to synchronize the value of a column bean and the corresponding cell value. The designer can add a listener on a column bean that's triggered when the user edits the cell. The event handler can then access the cell value through the column bean and set a value on another cell on the same row, also through a column bean. The code for this is easier to write and maintain than using a table model listener.

Design Time Rendering
As you can see in Figure 1, the text fields in the column beans are not empty. Instead they contain a text value that reflects a few important properties (a feature inherited from the base class DoiTextField): a mandatory column has an exclamation mark suffix, a numeric column is displayed with "#", "##" or "#.#" (depending on if it is an Integer, Long, or Double), an uppercase string column uses "ABC", etc.

Smart Design Time Checking
In some circumstances, checking for design-time mode is not sufficient. Some IDEs, for example, NetBeans, have a preview function that creates a window with the form inside it where the designer can try it out. The isDesignTime method still returns true, however, which causes DoiTable to think that it's still in design mode, and it doesn't convert itself. To get around this, it has its own isDesignTime method that first calls the standard method. If it returns false we are in "real" runtime, and no further checking is necessary; if it returns false an extra check for the special preview mode is necessary. This check is IDE dependent, and in the NetBeans case it is done by searching the parent container hierarchy for the innermost frame that has a title starting with "Testing Form[". Other IDEs will most likely need variations of this technique.

Conclusion
I hope I've provided you with some ideas that you can use when you write your own beans. The same principle can naturally be applied to very different kinds of widgets, especially complex ones that are easier to design with if they are broken up into parts. The time spent on doing this is earned many times over when the end-user GUIs are designed.

Resources

  • The Java Tutorial, trail JavaBeans: java.sun.com/docs/books/tutorial/javabeans/index.html
  • NetBeans FAQ - GUI Editing: www.netbeans.org/kb/faqs/gui_editing.html
  • About Gunnar Grim
    Gunnar Grim is a programmer, designer, and architect for the consulting firm Know IT (www.knowit.se). He has been in the business for 20 years, programming in everything from Z80 assembly code to SQL Windows. Since early 1996 he has worked almost exclusively with Java, mostly on the server side but also quite a lot with Swing.

    LATEST JAVA STORIES & POSTS
    Case Study: Java and the Mac
    This is the story of a Mac application developer (okay - it's about two of them) who set out on a quest to find an application development tool based on Java so his boss would let him develop on the Mac platform, which he loved. There was only one catch - he had to find a tool th
    A Lightweight Approach to SOA and BPM in Java Using jBPM
    SOA is mostly associated with technologies such as BPEL, SCA and Web Services. But does SOA really imply these technologies? In this session we will show how you can use the service oriented approach while staying inside the Java world. jBPM is a powerful lightweight framework th
    JavaOne 2008: Uncommon Java Bugs
    Any large Java source base can have insidious and subtle bugs. Every experienced Java programmer knows that finding and fixing these bugs can be difficult and costly. Fortunately, there are a large number of free open source Java tools available that can be used to find and fix d
    The 4 Core Principles of Agile Programming
    One of the things I really enjoy at the moment is the recognition and adoption of agile programming as a fully fledged powerful way to deliver quality software projects. As its figurehead is a group of very talented individuals who have created the agile manifesto (http://agilema
    JavaOne 2008: Sun Adds Comprehensive Video Capabilities to JavaFX
    Sun Microsystems announced it has entered into a multi-year agreement with On2 Technologies to add comprehensive video capabilities, using On2 Technologies TrueMotion video codecs, to Sun's JavaFX, a family of products for creating Rich Internet Applications (RIAs) with immersive
    JavaOne Archives - Dvorak Comments on AMD Intel Lawsuit on SYS-CON.TV
    Conference in San Francisco. Dvorak held forth on a number of topics, including the new AMD/Intel lawsuit, the viability of Java and Sun, the value of (or lack thereof) of corporate PR, and whether or not a new book about Silicon Valley is really worth reading.
    SUBSCRIBE TO THE WORLD'S MOST POWERFUL NEWSLETTERS
    SUBSCRIBE TO OUR RSS FEEDS & GET YOUR SYS-CON NEWS LIVE!
    Click to Add our RSS Feeds to the Service of Your Choice:
    Google Reader or Homepage Add to My Yahoo! Subscribe with Bloglines Subscribe in NewsGator Online
    myFeedster Add to My AOL Subscribe in Rojo Add 'Hugg' to Newsburst from CNET News.com Kinja Digest View Additional SYS-CON Feeds
    Publish Your Article! Please send it to editorial(at)sys-con.com!

    Advertise on this site! Contact advertising(at)sys-con.com! 201 802-3021

    SYS-CON FEATURED WHITEPAPERS

    ADS BY GOOGLE
    BREAKING JAVA NEWS
    KongZhong Corporation Reports Unaudited First Quarter 2008 Financial Results
    KongZhong Corporation , a leading wireless value-added services (WVAS) and wireless media co