|
|
YOUR FEEDBACK
Did you read today's front page stories & breaking news?
SOA World Conference
Virtualization Conference $200 Savings Expire May 16, 2008... – Register Today!
SYS-CON.TV |
TOP THREE LINKS YOU MUST CLICK ON Java Desktop
A GUI Painter Friendly Table Component
The principle of the column container
By: Gunnar Grim
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.
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 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
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 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 Runtime Propagation of Properties Runtime Synchronization of Cell Values Design Time Rendering Smart Design Time Checking Conclusion Resources LATEST JAVA STORIES & POSTS
SUBSCRIBE TO THE WORLD'S MOST POWERFUL NEWSLETTERS SUBSCRIBE TO OUR RSS FEEDS & GET YOUR SYS-CON NEWS LIVE!
|
SYS-CON FEATURED WHITEPAPERS MOST READ THIS WEEK BREAKING JAVA NEWS
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||