Browse DevX
Sign up for e-mail newsletters from DevX


Jazz Up Your JTables with Reusable Classes-3 : Page 3




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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 class ColorProvider 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

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date