|
Bookshelf Index
Chapter 18, Tables
By Matthew Robinson and Pavel Vorobiev
Introduction |
Download Chapter 18| Download Chapter 22 << NEXT >>
Class ColumnData
This class encapsulates data
describing the visual characteristics of a single
TableColumn of
our table. The instance variables
defined in this class have the following meaning:
String
m_title: column title
int m_width:
column width in pixels
int
m_alignment: text alignment as defined in
JLabel
The only constructor provided assigns
each of these variables the data passed as parameters.
Class StockTableData
This class extends AbstractTableModel to serve
as the data model for our table. Recall that
AbstractTableModel
is
an abstract class, and three
methods must be implemented to instantiate it:
public int
getRowCount(): returns the number of rows in the
table.
public int
getColumnCount(): returns the number of columns in the
table.
public
Object getValueAt(int row, int column): returns data in
the
specified
cell as an Object
instance.
Note: An
alternative approach is to extend the
DefaultTableModel
class which is a concrete implementation of
AbstractTableModel.
However, this is not recommended, as the few abstract methods in
AbstractTableModel can be easily
implemented. Usage of DefaultTableModel
often creates unnecessary overhead.
By design, this class manages all
information about our table, including the title and column data.
A
static
array of ColumnData, m_columns, is
provided
to
hold information about our table's columns (it is used in the
StocksTable constructor,
see above). Three instance variables have the following
meaning:
SimpleDateFormat
m_frm: used to format dates
Date m_date:
date of currently stored market data
Vector
m_vector: collection of StockData
instances for each row in the table
The only constructor of the StockTableData class
initializes two of these variables and calls the
setDefaultData()
method to assign the pre-defined
default data to m_date
and m_vector (in a
later example we'll see how to use JDBC to retrieve data from a
database
rather
than using hard-coded data as we do here).
As we discussed above, the getRowCount() and
getColumnCount() methods
should return the number of rows and columns, respectively. So
their
implementation is fairly obvious. The only catch is that they may
be
called by
the AbstractTableModel
constructor before any
member
variable
is initialized. So we have to check for a null
instance
of
m_vector. Note that m_columns, as a
static
variable,
will be
initialized before any non-static code is executed (so we don't
have
to check
m_columns against null).
The remainder of the StockTableData class
implements the following methods:
getColumnName():
returns the column title.
isCellEditable():
always returns false,
because we want to disable all editing.
getValueAt():
retrieves data for a given cell as an Object.
Depending
on the
column index, one of the StockData fields is
returned.
getTitle():
returns our table's title as a String
to be used in a JLabel
in the northern region of our frame's content pane.
Running the Code
Figure 18.1 shows StocksTable in action
displaying our hard-coded stock data. Note that the
TableColumns
resize properly in response to the parent frame size. Also note
that the selected row in our table can be moved by changed with
the
mouse or
arrow keys, but no editing is allowed.
18.3 Stocks Table: Part II - Custom Renderers
Now we'll extend StocksTable to use color
and small icons in rendering our table cells. To enhance data
visibility,
we'll
make the following two enhancements:
Render absolute and percent changes in green for positive
values and red for negative values.
Add an icon next to each stock symbol: arrow up for positive
changes and arrow down for negative.
To do this we need to build our own
custom TreeCellRenderer.

Click Figure 18.2 for full-scale image. JTable
using
a custom
cell renderer.
The Code: StocksTable.java
see \Chapter18\2
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.text.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class StocksTable
extends JFrame
{
// Unchanged code from
//section 18.2
public StocksTable() {
// Unchanged code from
//section 18.2
for (int k = 0; k <
StockTableData.m_columns.length;
k++) {
DefaultTableCellRenderer
renderer = new
ColoredTableCellRenderer();
renderer.setHorizontalAlignment(
StockTableData.m_columns[
k].m_alignment);
TableColumn column =
new TableColumn(k,
StockTableData.m_columns[
k].m_width, renderer, null);
m_table.addColumn(column);
}
// Unchanged code from
//section 18.2
}
public static void main(String argv[]) {
new StocksTable();
}
}
class ColoredTableCellRenderer
extends DefaultTableCellRenderer
{
public void setValue(Object value) {
if (value instanceof ColorData) {
ColorData cvalue = (ColorData)value;
setForeground(cvalue.m_color);
setText(cvalue.m_data.toString());
}
else if (value instanceof IconData) {
IconData ivalue = (IconData)value;
setIcon(ivalue.m_icon);
setText(ivalue.m_data.toString());
}
else
super.setValue(value);
}
}
class ColorData
{
public Color m_color;
public Object m_data;
public static Color GREEN =
new Color(0, 128, 0);
public static Color RED =
Color.red;
public ColorData(
Color color, Object data) {
m_color = color;
m_data = data;
}
public ColorData(Double data) {
m_color = data.doubleValue()
>= 0 ? GREEN : RED;
m_data = data;
}
public String toString() {
return m_data.toString();
}
}
class IconData
{
public ImageIcon m_icon;
public Object m_data;
public IconData(
ImageIcon icon, Object data) {
m_icon = icon;
m_data = data;
}
public String toString() {
return m_data.toString();
}
}
class StockData
{
public static ImageIcon ICON_UP =
new ImageIcon("ArrUp.gif");
public static ImageIcon ICON_DOWN =
new ImageIcon("ArrDown.gif");
public static ImageIcon ICON_BLANK =
new ImageIcon("blank.gif");
public IconData m_symbol;
public String m_name;
public Double m_last;
public Double m_open;
public ColorData m_change;
public ColorData m_changePr;
public Long m_volume;
public StockData(
String symbol, String name,
double last,
double open, double change, double
changePr, long volume) {
m_symbol =
new IconData(getIcon(
change), symbol);
m_name = name;
m_last = new Double(last);
m_open = new Double(open);
m_change = new ColorData(
new Double(change));
m_changePr = new ColorData(
new Double(changePr));
m_volume = new Long(volume);
}
public static ImageIcon
getIcon(double change) {
return (change>0 ? ICON_UP :
(change<0 ? ICON_DOWN :
ICON_BLANK));
}
}
// Class StockTableData unchanged
//from section 18.2
|
Understanding the Code
Class StocksTable
The only change we need to make in the base frame class is to
change
the
column
renderer to a new class, ColoredTableCellRenderer.
ColoredTableCellRenderer should be able to draw
icons and
colored
text, but not both at the same time---although this could be done
using this
same approach.
Class ColoredTableCellRenderer
This class extends DefaultTableCellRenderer
and overrides only one method: setValue().
This method will be called prior to the rendering of a cell to
retrieve its
corresponding data (of any nature) as an Object. Our
overridden
setValue() method is able to recognize
two
specific
kinds of cell
data: ColorData,
which adds color to a data object, and IconData,
which
adds an
icon
(both are described
below). If a ColorData
instance is detected, its encapsulated color is set as the
foreground
for the
renderer. If an IconData
instance is detected, its encapsulated icon is assigned to the
renderer with
the setIcon() method
(which is inherited from JLabel).
If the value is neither a ColorData
or an IconData
instance we call the super-class setValue()
method.
Class ColorData
This class is used to bind a specific
color, m_color, to a
data object of any nature, m_data.
Two static ColorsRED and
GREEN,
are declared to avoid creation of numerous temporary objects. Two
constructors
are provided for this class. The first constructor takes
Color
and
Object parameters and
assigns them to instance variables m_color and
m_data
respectively. The second constructor takes a Double
parameter
which
gets assigned to m_data, and m_color is
assigned the
green color if the parameter is positive, and red if negative.
The
toString() method simply
calls the toString()
method of the data object.
Class IconData
This class is used to bind ImageIcon m_icon to a
data
object of any nature, m_data.
Its only constructor takes ImageIcon
and Object
parameters. The toString() method
simply calls the toString()
method of the data object.
Class StockData
This class has been enhanced from its previous version to to
provide
images
and
new variable data types. We've
prepared three static ImageIcon
instances holding images: arrow up, arrow down, and a blank, all
transparent
image. The static getIcon()
method returns one of these images depending on the sign of the
given
double parameter. We've also
changed three instance variables to bind data with the color and
image
attributes according to the following table:
| Field
| New type | Data object | Description |
m_symbol | IconData | String | Stock's symbol (NYSE or
NASDAQ) |
m_change | ColorData | Double |
Absolute change in price |
m_changePr | ColorData | Double | Percent change in price |
The corresponding changes are also required in the
StockData
constructor.
Running the Code
Figure 18.2 shows StocksTable with custom
rendering in action. Note the correct usage of color and icons,
which
considerably enhances the visualization of our data.
Improving visual communication Tables can be data
intensive and consequently it can be very difficult for the
viewer to
quickly
pick out the important information. The table in fig 18.1
highlighted
this. In
fig 18.2, we are improving the visual communication with the
introduction of
visual layers. The icons in the first column quickly tell the
viewer
whether a
price is rising or falling. This is visually re-inforced with the
red
and
green
introduced on the change columns.
Red particularly is a very
strong color. By introducing red and green only on the change
columns
and not
across the entire row, we avoid the danger of the red becoming
overpowering.
If
we had introduced red and green across the full width of the
table,
the colors
may have become intrusive and impaired the visual
communication.
18.4Stocks Table: part III - Data Formatting
To further enhance the presentation
of our stock data, in this section we wll take into account that
actual stock
prices (at least on NYSE and NASDAQ) are expressed in fractions
of 32,
not in
decimals. Another issue that we will deal with is the volume of
trade,
which
can reach hundreds of millions of dollars. Volume is not
immediately
legible
without separating thousands and millions places with commas.
Figure 18.3 JTable with custom
number formatting cell renderer to display fractions and
comma-delimited
numbers.
The Code: StocksTable.java
see \Chapter18\3
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.text.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;
// Unchanged code from
//section 18.3
class Fraction
{
public int m_whole;
public int m_nom;
public int m_den;
public Fraction(double value) {
int sign = value <0 ? -1 : 1;
value = Math.abs(value);
m_whole = (int)value;
m_den = 32;
m_nom = (int)((value-m_whole)*m_den);
while (m_nom!=0 && m_nom%2==0) {
m_nom /= 2;
m_den /= 2;
}
if (m_whole==0)
m_nom *= sign;
else
m_whole *= sign;
}
public double doubleValue() {
return (double)m_whole +
(double)m_nom/m_den;
}
public String toString() {
if (m_nom==0)
return ""+m_whole;
else if (m_whole==0)
return ""+m_nom+"/"+m_den;
else
return ""+m_whole+"
"+m_nom+"/"+m_den;
}
}
class SmartLong
{
protected static
NumberFormat FORMAT;
static {
FORMAT =
NumberFormat.getInstance();
FORMAT.setGroupingUsed(true);
}
public long m_value;
public SmartLong(long value)
{ m_value = value; }
public long longValue()
{ return m_value; }
public String toString()
{ return FORMAT.format(
m_value); }
}
class ColorData
{
public ColorData(Fraction data) {
m_color = data.doubleValue()
>= 0 ? GREEN : RED;
m_data = data;
}
// Unchanged code from section 18.3
}
class StockData
{
public static ImageIcon ICON_UP =
new ImageIcon("ArrUp.gif");
public static ImageIcon ICON_DOWN =
new ImageIcon("ArrDown.gif");
public static ImageIcon ICON_BLANK =
new ImageIcon("blank.gif");
public IconData m_symbol;
public String m_name;
public Fraction m_last;
public Fraction m_open;
public ColorData m_change;
public ColorData m_changePr;
public SmartLong m_volume;
public StockData(String symbol,
String name,
double last,
double open, double change,
double changePr, long volume) {
m_symbol =
new IconData(getIcon(
change), symbol);
m_name = name;
m_last = new Fraction(last);
m_open = new Fraction(open);
m_change = new ColorData(
new Fraction(change));
m_changePr = new ColorData(
new Double(changePr));
m_volume = new SmartLong(volume);
}
// Unchanged code from section 18.3
}
|
Understanding the Code
Class Fraction
This new data class encapsulates
fractions with denominator 32 (or in a reduced form). Three
instance
variables
represent the whole number, numerator, and denominator of the
fraction. The
only constructor takes a double parameter and carefully extracts
these
values,
performing numerator and denominator reduction, if possible. Note
that
negative
absolute values are taken into account.
Method doubleValue() performs the opposite task: it
converts the fraction into a double
value. Method toString()
forms a String
representation of the fraction.
Note that a zero whole number or a
zero nominator are omitted to avoid unseemly output like "0
1/2" or
"12 0/32".
Class SmartLong
This class encapsulates long values. The only
constructor takes a long
as parameter and stores it in the m_value instance
variable. The
real purpose of creating this
class is the overridden toString()
method, which inserts commas separating thousands, millions, etc.
places. For
this purpose we use the java.text.NumberFormat
class. An instance of this class is created as a static variable,
and
formatting values using NumberFormat's
format() method
couldn't be easier.
Note:
SmartLong cannot extend Long (although
it would be natural), because java.lang.Long
is a final class.
Note: We could use the different approach of just
formatting
the
initial
contents of our table's cells into text strings and perating on
them
without
introducing new data classes. This approach, however, is not
desirable from a
design point of view, as it strips the data of its true nature:
numbers.
Page
1
2
3
4
5
6
7
8
9
10
11
<< NEXT >>
Download Chapter 18| Download Chapter 22]
|