devxlogo

Jazz Up Your JTables with Reusable Classes

Jazz Up Your JTables with Reusable Classes

ome time ago, I was faced with a tricky user interface problem: develop a suite of real-time Swing stock displays that flash and color themselves according to the data on the screen. It didn’t seem too hard?except that my users wanted total control. They each wanted to select the row colors and cell fonts themselves. Some wanted pink, while others wanted blue.

After a bit of research, I couldn’t find any well-known method for doing this with JTable components. Sure, I could have written lots of custom rendering code, but I had more than 20 displays to implement and not much time. I needed a simple, flexible approach that I could reuse for every display.

I eventually discovered a solution: the Decorator (GOF) design pattern. The Decorator pattern allows components to be arbitrarily combined or enclosed. Think of it as a Russian Doll technique. Components are wrapped one inside the other so that they can work together for a particular task or purpose. By using the Decorator pattern, I can create a few simple custom renderers and then combine them in various ways to achieve different effects. In addition, by laying down a set of well-defined interfaces to encapsulate my display data I can have all the building blocks I need.

JTable Rendering
Rendering is the mechanism by which Swing paints user interface components on the screen. In JTables, table cell renderers control rendering. They are responsible for drawing individual cells. They control the formatting of the data in the cell: the font, border, and background and foreground colors. Renderers are shared across cells. Think of them as rubber-stamps (Swing tutorial) that move from cell to cell, formatting and displaying data from the table model.

Table cell renderers may be attached to columns or to Java types. Swing provides a rudimentary set of pre-built renderers, which support the simple formatting and display of such Java types as Boolean, Image, ImageIcon, Date, Double, Float, Number, and Object. You don’t have to do anything special to obtain these renderers; you get them for free with every JTable.





How do I develop a suite of real-time JTable displays that automatically flash and color themselves according to data updates on the screen without writing a lot of custom rendering code?



Use the Decorator pattern to create custom renderers and then combine them in various ways to achieve different effects. Acquire all the building blocks you need by laying down a set of well-defined interfaces to encapsulate your display data.

Custom Rendering?Row Colors and Highlights
Most of the time you don’t have to think about rendering, because Swing handles it all for you. However, to do something different from what Swing offers out of the box you must create specialized pieces of code to implement your specific look and feel. These are called custom renderers.

The first step in creating a custom renderer is to create a class that implements the Swing interface TableCellRenderer. All table renderers must implement this interface. One of its methods, getTableCellRendererComponent(), returns a UI component that you can use to display a value in a table cell. Swing provides a default implementation of the TableCellRenderer interface called DefaultTableCellRenderer. It subclasses JLabel and returns itself (this) from the getTableCellRendererComponent() method. For custom renderers you can either extend DefaultTableCellRenderer or implement your own from scratch. Either way works.

Using the Decorator Pattern
To control the look and feel in a table, a renderer must be able to manipulate the formatting, colors, fonts, and borders of a cell. Most custom renderers are built to solve a particular UI problem, which means they are usually problem specific and need to be rewritten for every display. Another approach is available, however.

Rather than creating a large renderer for each JTable, consider breaking it down. Create a family of small general-purpose, reusable renderers, each responsible for one aspect of a cell’s appearance. Use a ColorRenderer to control colors, a FontRenderer to control fonts, and a BorderRenderer to control Borders (see Figure 1).

Figure 1: Decorator Renderers

In addition to being generic and re-usable, renderers can be designed to work with each other using the Decorator pattern. You can implement this technique by creating a constructor on a renderer that takes as an argument another renderer. That is, the renderer is able to wrap an existing renderer (such as the default renderers supplied by the JTable) to take advantage of existing functionality. Take the following snippet:

public class ColorRenderer implements TableCellRenderer {  protected TableCellRenderer delegate;   public ColorRenderer(TableCellRenderer anotherRenderer) {    this.delegate = anotherRenderer;}...};

The ColorRenderer takes advantage of the functionality that the passed-in renderer (or its “delegate”) inside the getTableCellRendererComponent() method provides. This is the method that performs the Swing work associated with painting a cell. It calls the delegate to obtain the UI component and then applies its own color-specific behavior before returning to the caller:

public Component getTableCellRendererComponent(JTable table,    Object value,    boolean isSelected,    boolean hasFocus,    int row,    int column) {    Color bgrd = null;    Color fgrd = null;    if (isSelected) {      // Preserve selection colors      fgrd = table.getSelectionForeground();      bgrd = table.getSelectionBackground();    } else {      // Set our colours      fgrd = Color.pink;      bgrd = Color.blue;      Component c =                delegate.getTableCellRendererComponent(table, value, isSelected, 
hasFocus, row, column); // Set the component colours c.setBackground(bgrd); c.setForeground(fgrd); return c; }

Separating Renderers from the Application
Using the Decorator pattern is powerful, but hardcoding the colors within the renderer limits the reusability of the component. Ideally, the application or the data inside the table should drive the colors. A simple way to enable this is to separate the responsibilities into a pure renderer (that performs only Swing work) and a Provider class that is responsible for setting an application’s specific look and feel. This way, the renderer can be kept general (and, hence, reusable), and the Provider can encapsulate the application’s UI characteristics.

You can formalize the renderer/provider relationship using an interface, which another part of the application can implement. Think of the Provider as the renderers’ connection to the outside world:

public interface ColorProvider {  public Color getForeground(int row, int column);  public Color getBackground(int row, int column);}

The ColorRenderer now becomes slightly more sophisticated. Pass in the ColorProvider via the constructor:

public class ColorRenderer implements TableCellRenderer {  protected TableCellRenderer delegate;  protected ColorProvider provider;  public ColorRenderer(TableCellRenderer anotherRenderer, ColorProvider provider) {    this.delegate = anotherRenderer;    this.provider = provider;  }...}

Use the ColorProvider in the getTableCellRendererComponent() method to determine the colors of a cell:

public Component getTableCellRendererComponent(...) {    Color bgrd = null;    Color fgrd = null;    if (isSelected) {      fgrd = table.getSelectionForeground();      bgrd = table.getSelectionBackground();    } else {      // Adjust for columns moving around      int mcol = table.convertColumnIndexToModel(column);      // Get the colors from the provider      fgrd = provider.getForeground(row, mcol);      bgrd = provider.getBackground(row, mcol);    }    Component c =      delegate.getTableCellRendererComponent(table, value, isSelected, 
hasFocus, row, column); // Set the component colours c.setBackground(bgrd); c.setForeground(fgrd); return c; }};

Making Renderers Data-sensitive
The next step is to supply a ColorProvider implementation that will automatically adjust the colors of a cell according to the value of the data in the table. For example, imagine that you have a JTable display that contains broker’s orders. Each row shows one order. Each order has the following columns: symbol, quantity, price, side (buy or sell), and status. The rows must be colored according to the status of the order. The status updates in real-time and the row must respond and change color accordingly.

A TableModel is an application-specific class that contains the data shown in a JTable. It controls the number of rows, the number of columns, the column names, the column types, and the data itself. The OrderTableModel (see Listing 1) manages an array of broker’s orders and has to notify the JTable whenever an order status update comes in (via the updateOrderStatus() method). This notification tells the JTable that some data has changed and that it should re-render the modified cell or row. The JTable will then call the ColorRenderer, which will adjust the color of the cell in response to the update.

To connect the ColorRenderer so that it shows the orders in the appropriate colors, create an implementation of the ColorProvider interface. The implementing class accesses the OrderTableModel to fetch the status of an order in a row. Pass the ColorProvider implementation into the ColorRenderer constructor when the renderer is instantiated:

OrderTableModel model = new OrderTableModel();final static Color newColor = new Color(194, 245, 254);final static Color filledColor = new Color(255, 221, 254);final static Color cancelledColor = new Color(221, 254, 221);// The provider here implemented as an anonymous inner classColorProvider provider = new ColorProvider() {    public Color getForeground(int row, int column) {      // Show the price column in red      if (column == 2)        return Color.red;      else        return Color.black;    }    public Color getBackground(int row, int column) {      if (model.isNew(row))        return Color.NewColor;      else if (model.isFilled(row))        return filledColor;      else        return cancelledColor;    }  };  

Registering Custom Renderers
Unless otherwise directed, a JTable will use its built-in default renderers. To use a custom renderer, you must register it with the JTable using the setDefaultRenderer() method. Since the ColorRenderer is designed to work with an existing renderer, it is wrapped around the standard JTable default renderer to take advantage of its default formatting capabilities.

You must register the renderer for all Java types (classes) that are used inside the OrderTableModel:

private void registerRendererForClass(JTable table, Class klass) {  // Get Default Renderer from the table  DefaultTableCellRenderer defaultRenderer =      (DefaultTableCellRenderer)     table.getDefaultRenderer(klass);  // Wrap(Decorate) the color renderer around the default renderer  TableCellRenderer colorRenderer = new      ColorRenderer(defaultRenderer, provider);  // Register the color Renderer with the JTable  table.setDefaultRenderer(klass, colorRenderer);}

Use the registerRendererForClass() method to register the ColorRenderer for use in all cells that contain Double and String values:

JTable table = new JTable(model);registerRendererForClass(table,String.class);registerRendererForClass(table,Number.class);

With the renderer, provider, and table model in place, a JTable display looks like Figure 2.

Figure 2: A Broker’s Order Display

Row and Cell Flashing
Following the pattern established by the ColorRenderer, you can set up custom renderer/provider pairs to manage fonts, borders, and other effects. Row and cell flashing is no different. In the Broker’s Order display, rows flash whenever a price change occurs. A custom FlashColorRenderer and FlashProvider class/interface pair can control the flash effects:

public interface FlashProvider {  public boolean isFlashOn(int row, int column);};

As an example, create a FlashColorRenderer that uses the Decorator pattern just like the ColorRenderer. It should have a constructor that takes two arguments, the first a renderer and the second a FlashProvider:

public class FlashColorRenderer implements TableCellRenderer {  protected TableCellRenderer delegate;  protected FlashProvider provider;  public FlashColorRenderer(TableCellRenderer anotherRenderer, 
FlashProvider provider) { this.delegate = anotherRenderer; this.provider = provider; }...};

Inside the getTableCellRendererComponent() method, use the FlashProvider to get the flash state of a cell and then flash accordingly:

public Component getTableCellRendererComponent(...) {    // Get the component from the delegate    Component c =      delegate.getTableCellRendererComponent(table, value, isSelected, 
hasFocus, row, column); // Convert view column to model column int mcol = table.convertColumnIndexToModel(column); // invert the colours to flash if (provider.isFlashOn(row, mcol)) { Color bgrd = c.getForeground(); Color fgrd = c.getBackground(); c.setBackground(bgrd); c.setForeground(fgrd); } return c;}

Wrap the FlashColorRenderer around the ColorRenderer so that you can use both effectively. Wrapping can continue endlessly?each layer adding its own specific behavior:

private void registerRendererForClass(JTable table,Class klass) {    // Get Default Renderer from the table    DefaultTableCellRenderer defaultRenderer =      (DefaultTableCellRenderer) table.getDefaultRenderer(klass);    // Wrap the color renderer around the default renderer    TableCellRenderer colorRenderer = new ColorRenderer(defaultRenderer, provider);    // Wrap the flash renderer around the colour renderer    TableCellRenderer flashRenderer = new FlashColorRenderer(colorRenderer, 
flashProvider); // Register our flash renderer with the table table.setDefaultRenderer(klass, flashRenderer); }

The final step to get the flashing feature working is to create a TableFlasher class that implements the FlashProvder interface to control the flash state of a cell (see Listing 2). Application code that updates order prices must also notify the TableFlasher of the update (Author’s note: SWING is single threaded, so be sure to use the invokeLater() functionality when executing code that accesses UI components outside the Swing event thread.):

TableFlasher flashProvider = new TableFlasher(table);...// In a background thread, pick a rowint row = (int) (Math.random() * model.getRowCount());// Generate a new priceDouble price = new Double(Math.random() * 100);// Update the OrderTableModelmodel.updateOrderPrice(row, price); // Flash the price cellflashProvider.flashCell(row, 2);...

With the renderer, provider, and table model in place, a JTable display looks like Figure 3.

Figure 3: A Flashing Broker’s Order Display

3-D Border Affects
You can simulate 3-D affects by manipulating the border of a cell. Create a FlashBorderRenderer, which?like the FlashColorRenderer?uses the FlashProvider interface to determine the flash state of a cell. Inside the getTableCellRendererComponent() method, set the Border of the cell to raised to give it a 3-D effect:

public class FlashBorderRenderer {protected static final Border raised = BorderFactory.createRaisedBevelBorder();  protected static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);  protected static final Border focusBorder = 
UIManager.getBorder("Table.focusCellHighlightBorder");...public Component getTableCellRendererComponent(...) { // Get the component from the delegate Component c = delegate.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column); // Convert view column to model column int mcol = table.convertColumnIndexToModel(column); // invert the colours to flash JComponent jc = (JComponent) c; // If an abstract button e.g JCheckBox then set // border painted to true if (jc instanceof AbstractButton) { ((AbstractButton) jc).setBorderPainted(true); } if (provider.isFlashOn(row, mcol)) { jc.setBorder(raised); } else { if (hasFocus) jc.setBorder(focusBorder); else jc.setBorder(noFocusBorder); } return c; }

Remember to register the BorderFlashRenderer inside the registerRendererForClass() method to add those 3-D flashing effects to the existing behavior.

Reusable Classes for All JTable Display Effects
Using the techniques outlined in this article, you built a few common classes that you can re-use for all your JTable display effects. With the Decorator pattern, you can dynamically bind renderers together to create complex combinations of user interface behavior. Applying the Provider interface decouples the renderers from the application and makes them portable, which means no more hard coding! So have fun adding color, border, and font effects to your JTables.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist