Drawing Swing JTable rows with different colors using TableCellRenderer interface Example
In this example we are going to demonstrate how to use Java Swing TableCellRenderer
interface to customize cell rendering. When a JTable
object is created, several default renderers are also created. Those renderers are capable of rendering Boolean choices, dates, image icons, numbers, and objects (as strings). If you do not explicitly attach a custom render to a column, a table component chooses a default renderer on your behalf. However, If you discover that a table component’s default renderers do not meet your needs. At that time, you will want to develop a custom renderer via an implementation of TableCellRenderer
. So, we will show TableCellRenderer
implementation in a real business case.
Let’s suppose that we need to create an application which monitor the stock market shares prices, it shows which share goes down and up. So, we need to show these share attributes (Symbol
, Company Name
,Price
, Change
, % Change
, Volume
). In addition, we also need to color each share price Change
and % Change
in Green when it goes up and in Red when it goes down.
To accomplish that task, we should do two things. First, we create a class that implements the TableCellRenderer
interface and overrides that interface’s getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
method to return a Component reference to an object that performs the actual rendering. Second, we create an object from your class and call JTable’s setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer)
method to establish that object as a cell renderer.
1. Custom Table Cell Renderer
We create PriceChangeColorRenderer.java
class to play as a custom price cell renderer where it renders the Change
and % Change
cells text color using setForeground(Color c)
based on the Change
cell value, if it’s greater than zero, the color will be Green. However, if it’s less than zero, the color will be Red.
Also, we apply the zebra style on the JTable
rows using setBackground(Color c)
as the following:
// Apply zebra style on table rows if (row % 2 == 0) { c.setBackground(Constants.EVEN_ROW_COLOR); } else { c.setBackground(Constants.ODD_ROW_COLOR); }
PriceChangeColorRenderer.java
package com.jcg; import java.awt.Color; import java.awt.Component; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; /** * @author ashraf_sarhan * */ public class PriceChangeColorRenderer implements TableCellRenderer { public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer(); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = DEFAULT_RENDERER.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); // Apply zebra style on table rows if (row % 2 == 0) { c.setBackground(Constants.EVEN_ROW_COLOR); } else { c.setBackground(Constants.ODD_ROW_COLOR); } if (column == Constants.CHANGE_IDX || column == Constants.PERCENTAGE_CHANGE_IDX) { Object priceChangeObj = table.getModel().getValueAt(row, Constants.CHANGE_IDX); double priceChange = Double.parseDouble(priceChangeObj.toString()); Color color; if (priceChange > 0) { color = Constants.PRICE_UP_COLOR; } else { color = Constants.PRICE_DOWN_COLOR; } c.setForeground(color); } else { c.setForeground(Constants.DEFAULT_FOREGROUND_COLOR); } return c; } }
2. Stock Market Price Data Simulator
We create TableDataFeeder.java
class to simulate the stock market prices live feed with only ten stocks and load it into the Stocks Market Data
table. Also, we simulate the price changes by selecting a random stock then decrease or increase its price by a random price delta on every call for the feedData(JTable table)
.
TableDataFeeder.java
package com.jcg; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.concurrent.ThreadLocalRandom; import javax.swing.JTable; import javax.swing.table.DefaultTableModel; /** * @author ashraf_sarhan * */ public class TableDataFeeder { private static boolean isPriceUp = true; public static void feedData(JTable table) { // Get a random index to apply the price update int randomRowIdx = ThreadLocalRandom.current().nextInt(0, 10); updatePrice(table, randomRowIdx); } private static void updatePrice(JTable table, int row) { DefaultTableModel model = (DefaultTableModel) table.getModel(); Double oldPrice = (Double) model.getValueAt(row, Constants.PRICE_IDX); Double newPrice = 0.0; Double priceChange = 0.0; Double priceChangePercentage = 0.0; String priceChangePercentageStr = ""; Double priceDelta = ThreadLocalRandom.current().nextDouble( Constants.PRICE_CHANGE_RANGE[0], Constants.PRICE_CHANGE_RANGE[1]); if (isPriceUp) { // Get a newer bigger price value newPrice = oldPrice + priceDelta; isPriceUp = false; } else { // Get a newer smaller price value newPrice = oldPrice - priceDelta; isPriceUp = true; } if (newPrice > 0.0) { priceChange = newPrice - oldPrice; priceChangePercentage = roundDouble(new Double( (newPrice / oldPrice - 1) * 100), Constants.ROUND_PLACES); if (priceChangePercentage > 0) { priceChangePercentageStr = Constants.PLUS_SIGN + priceChangePercentage.toString() + Constants.PERCENTAGE_SIGN; } else { priceChangePercentageStr = priceChangePercentage.toString() + Constants.PERCENTAGE_SIGN; } // Update table row with the new values model.setValueAt(roundDouble(newPrice, Constants.ROUND_PLACES), row, Constants.PRICE_IDX); model.setValueAt(roundDouble(priceChange, Constants.ROUND_PLACES), row, Constants.CHANGE_IDX); model.setValueAt(priceChangePercentageStr, row, Constants.PERCENTAGE_CHANGE_IDX); } } private static Double roundDouble(double value, int places) { if (places < 0) throw new IllegalArgumentException(); BigDecimal bd = new BigDecimal(value); bd = bd.setScale(places, RoundingMode.HALF_UP); return bd.doubleValue(); } }
Also, we have a supplementary class Constants.java
which contains our constants.
Constants.java
package com.jcg; import java.awt.Color; /** * @author ashraf_sarhan * */ public class Constants { public static final Object[] TABLE_HEADER = { "Symbol", "Company Name", "Price", "Change", "% Change", "Volume" }; public static final Object[][] DATA = { { "BAC", "Bank of America Corporation", 15.98, 0.14, "+0.88%", 32157250 }, { "AAPL", "Apple Inc.", 126.57, -1.97, "-1.54%", 31367143 }, { "ABBV", "AbbVie Inc.", 57.84, -2.43, "-4.03%", 30620258 }, { "ECA", "Encana Corporation", 11.74, -0.53, "-4.33%", 27317436 }, { "VALE", "Vale S.A.", 6.55, -0.33, "-4.80%", 19764400 }, { "FB", "Facebook, Inc.", 81.53, 0.64, "+0.78%", 16909729 }, { "PBR", "Petróleo Brasileiro S.A. - Petrobras", 6.05, -0.12, "-2.02%", 16181759 }, { "NOK", "Nokia Corporation", 8.06, 0.01, "+0.12%", 13611860 }, { "PCYC", "Pharmacyclics Inc.", 254.67, 24.19, "+10.50%", 13737834 }, { "RAD", "Rite Aid Corporation", 7.87, -0.18, "-2.24%", 13606253 } }; public static final int DATA_REFRESH_RATE = 3000; public static final Color PRICE_UP_COLOR = Color.GREEN; public static final Color PRICE_DOWN_COLOR = Color.RED; public static final Color DEFAULT_FOREGROUND_COLOR = Color.BLACK; public static final Color ODD_ROW_COLOR = Color.decode("#F8F8F8"); public static final Color EVEN_ROW_COLOR = Color.WHITE; public static final String PLUS_SIGN = "+"; public static final String PERCENTAGE_SIGN = "%"; public static final int ROUND_PLACES = 2; public static final double[] PRICE_CHANGE_RANGE = { 0.1, 0.5 }; public static final int PRICE_IDX = 2; public static final int CHANGE_IDX = 3; public static final int PERCENTAGE_CHANGE_IDX = 4; }
3. Test Table Cell Renderer
We create TableCellRendererDemo.java
class to test our example where we instantiate a table
object of JTable
class and another new object colorRenderer
of PriceChangeColorRenderer
class, then we call setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer)
on the table
object to play as table cell renderer.
TableCellRendererDemo.java
package com.jcg; import java.awt.Dimension; import javax.swing.BorderFactory; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import javax.swing.table.DefaultTableModel; /** * @author ashraf_sarhan * */ @SuppressWarnings("serial") public class TableCellRendererDemo extends JPanel { private static JTable table; public TableCellRendererDemo() { DefaultTableModel model = new DefaultTableModel(Constants.DATA, Constants.TABLE_HEADER); table = new JTable(model); // Set custom price color renderer PriceChangeColorRenderer colorRenderer = new PriceChangeColorRenderer(); table.setDefaultRenderer(Object.class, colorRenderer); JScrollPane scrollpane = new JScrollPane(table); scrollpane.setPreferredSize(new Dimension(700, 182)); scrollpane.setViewportView(table); JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), "Market Movers", TitledBorder.CENTER, TitledBorder.TOP)); panel.add(scrollpane); add(panel); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { createAndShowGUI(); } catch (Exception e) { e.printStackTrace(); } } }); new Thread(new Runnable() { public void run() { while (true) { try { SwingUtilities.invokeLater(new Runnable() { public void run() { TableDataFeeder.feedData(table); } }); Thread.sleep(Constants.DATA_REFRESH_RATE); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } public static void createAndShowGUI() throws Exception { JFrame frame = new JFrame("Stocks Market Data"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new TableCellRendererDemo()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }
Output:
4. Download the Eclipse Project
This was an example on how to draw Swing JTable
rows with different colors using TableCellRenderer
interface.
You can download the full source code of this example here: TableCellRendererExampleCode.zip