JTable

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:

stocks-market-data
Figure 1: Stocks Market Data

 

4. Download the Eclipse Project

This was an example on how to draw Swing JTable rows with different colors using TableCellRenderer interface.

Download
You can download the full source code of this example here: TableCellRendererExampleCode.zip

Ashraf Sarhan

Ashraf Sarhan is a passionate software engineer, an open source enthusiast, has a Bsc. degree in Computer and Information Systems from Alexandria University. He is experienced in building large, scalable and distributed enterprise applications/service in multiple domains. He also has a keen interest in JavaEE, SOA, Agile and Big Data technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button