Updatable Highlight Currency Table
/* Core SWING Advanced Programming By Kim Topley ISBN: 0 13 083292 8 Publisher: Prentice Hall */ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.NumberFormat; import java.util.EventObject; import javax.swing.CellEditor; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.EventListenerList; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; public class UpdatableHighlightCurrencyTable { public static void main(String[] args) { try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); } catch (Exception evt) {} JFrame f = new JFrame("Updatable Highlighted Currency Table"); JTable tbl = new JTable(new TestUpdatableCurrencyTableModel()); tbl.setDefaultRenderer(java.lang.Number.class, new FractionCellRenderer(10, 3, SwingConstants.RIGHT)); TableColumnModel tcm = tbl.getColumnModel(); tcm.getColumn(0).setPreferredWidth(150); tcm.getColumn(0).setMinWidth(150); TextWithIconCellRenderer renderer = new TextWithIconCellRenderer(); tcm.getColumn(0).setCellRenderer(renderer); tbl.setShowHorizontalLines(false); tbl.setIntercellSpacing(new Dimension(1, 0)); // Add the stripe renderer in the leftmost four columns. StripedTableCellRenderer.installInColumn(tbl, 0, Color.lightGray, Color.white, null, null); StripedTableCellRenderer.installInColumn(tbl, 1, Color.lightGray, Color.white, null, null); StripedTableCellRenderer.installInColumn(tbl, 2, Color.lightGray, Color.white, null, null); StripedTableCellRenderer.installInColumn(tbl, 3, Color.lightGray, Color.white, null, null); // Add the highlight renderer to the difference column. // The following comparator makes it highlight // cells with negative numeric values. Comparator cmp = new Comparator() { public boolean shouldHighlight(JTable tbl, Object value, int row, int column) { if (value instanceof Number) { double columnValue = ((Number) value).doubleValue(); return columnValue < (double) 0.0; } return false; } }; tcm.getColumn(3).setCellRenderer( new HighlightRenderer(cmp, null, Color.pink, Color.black, Color.pink.darker(), Color.white)); // Install a button renderer in the last column ButtonRenderer buttonRenderer = new ButtonRenderer(); buttonRenderer.setForeground(Color.blue); buttonRenderer.setBackground(Color.lightGray); tcm.getColumn(4).setCellRenderer(buttonRenderer); // Install a button editor in the last column TableCellEditor editor = new ButtonEditor(new JButton()); tcm.getColumn(4).setCellEditor(editor); // Make the rows wide enough to take the buttons tbl.setRowHeight(20); tbl.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); tbl.setPreferredScrollableViewportSize(tbl.getPreferredSize()); JScrollPane sp = new JScrollPane(tbl); f.getContentPane().add(sp, "Center"); f.pack(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); f.setVisible(true); } } class TestUpdatableCurrencyTableModel extends UpdatableCurrencyTableModel { public void updateTable(Object value, int row, int column) { System.out.println("Update for row " + row + " required."); System.out.println("Values are " + getValueAt(row, 1) + ", " + getValueAt(row, 2) + "; diff is " + getValueAt(row, 3)); } } class FractionCellRenderer extends DefaultTableCellRenderer { public FractionCellRenderer(int integer, int fraction, int align) { this.integer = integer; // maximum integer digits this.fraction = fraction; // exact number of fraction digits this.align = align; // alignment (LEFT, CENTER, RIGHT) } protected void setValue(Object value) { if (value != null && value instanceof Number) { formatter.setMaximumIntegerDigits(integer); formatter.setMaximumFractionDigits(fraction); formatter.setMinimumFractionDigits(fraction); setText(formatter.format(((Number) value).doubleValue())); } else { super.setValue(value); } setHorizontalAlignment(align); } protected int integer; protected int fraction; protected int align; protected static NumberFormat formatter = NumberFormat.getInstance(); } class TextWithIconCellRenderer extends DefaultTableCellRenderer { protected void setValue(Object value) { if (value instanceof DataWithIcon) { if (value != null) { DataWithIcon d = (DataWithIcon) value; Object dataValue = d.getData(); setText(dataValue == null ? "" : dataValue.toString()); setIcon(d.getIcon()); setHorizontalTextPosition(SwingConstants.RIGHT); setVerticalTextPosition(SwingConstants.CENTER); setHorizontalAlignment(SwingConstants.LEFT); setVerticalAlignment(SwingConstants.CENTER); } else { setText(""); setIcon(null); } } else { super.setValue(value); } } } class DataWithIcon { public DataWithIcon(Object data, Icon icon) { this.data = data; this.icon = icon; } public Icon getIcon() { return icon; } public Object getData() { return data; } public String toString() { return data.toString(); } protected Icon icon; protected Object data; } class StripedTableCellRenderer implements TableCellRenderer { public StripedTableCellRenderer(TableCellRenderer targetRenderer, Color evenBack, Color evenFore, Color oddBack, Color oddFore) { this.targetRenderer = targetRenderer; this.evenBack = evenBack; this.evenFore = evenFore; this.oddBack = oddBack; this.oddFore = oddFore; } // Implementation of TableCellRenderer interface public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { TableCellRenderer renderer = targetRenderer; if (renderer == null) { // Get default renderer from the table renderer = table.getDefaultRenderer(table.getColumnClass(column)); } // Let the real renderer create the component Component comp = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); // Now apply the stripe effect if (isSelected == false && hasFocus == false) { if ((row & 1) == 0) { comp.setBackground(evenBack != null ? evenBack : table .getBackground()); comp.setForeground(evenFore != null ? evenFore : table .getForeground()); } else { comp.setBackground(oddBack != null ? oddBack : table .getBackground()); comp.setForeground(oddFore != null ? oddFore : table .getForeground()); } } return comp; } // Convenience method to apply this renderer to single column public static void installInColumn(JTable table, int columnIndex, Color evenBack, Color evenFore, Color oddBack, Color oddFore) { TableColumn tc = table.getColumnModel().getColumn(columnIndex); // Get the cell renderer for this column, if any TableCellRenderer targetRenderer = tc.getCellRenderer(); // Create a new StripedTableCellRenderer and install it tc.setCellRenderer(new StripedTableCellRenderer(targetRenderer, evenBack, evenFore, oddBack, oddFore)); } // Convenience method to apply this renderer to an entire table public static void installInTable(JTable table, Color evenBack, Color evenFore, Color oddBack, Color oddFore) { StripedTableCellRenderer sharedInstance = null; int columns = table.getColumnCount(); for (int i = 0; i < columns; i++) { TableColumn tc = table.getColumnModel().getColumn(i); TableCellRenderer targetRenderer = tc.getCellRenderer(); if (targetRenderer != null) { // This column has a specific renderer tc.setCellRenderer(new StripedTableCellRenderer(targetRenderer, evenBack, evenFore, oddBack, oddFore)); } else { // This column uses a class renderer - use a shared renderer if (sharedInstance == null) { sharedInstance = new StripedTableCellRenderer(null, evenBack, evenFore, oddBack, oddFore); } tc.setCellRenderer(sharedInstance); } } } protected TableCellRenderer targetRenderer; protected Color evenBack; protected Color evenFore; protected Color oddBack; protected Color oddFore; } class HighlightRenderer implements TableCellRenderer { public HighlightRenderer(Comparator cmp, TableCellRenderer targetRenderer, Color backColor, Color foreColor, Color highlightBack, Color highlightFore) { this.cmp = cmp; this.targetRenderer = targetRenderer; this.backColor = backColor; this.foreColor = foreColor; this.highlightBack = highlightBack; this.highlightFore = highlightFore; } public Component getTableCellRendererComponent(JTable tbl, Object value, boolean isSelected, boolean hasFocus, int row, int column) { TableCellRenderer renderer = targetRenderer; if (renderer == null) { renderer = tbl.getDefaultRenderer(tbl.getColumnClass(column)); } Component comp = renderer.getTableCellRendererComponent(tbl, value, isSelected, hasFocus, row, column); if (isSelected == false && hasFocus == false && value != null) { if (cmp.shouldHighlight(tbl, value, row, column)) { comp.setForeground(highlightFore); comp.setBackground(highlightBack); } else { comp.setForeground(foreColor); comp.setBackground(backColor); } } return comp; } protected Comparator cmp; protected TableCellRenderer targetRenderer; protected Color backColor; protected Color foreColor; protected Color highlightBack; protected Color highlightFore; } interface Comparator { public abstract boolean shouldHighlight(JTable tbl, Object value, int row, int column); } abstract class UpdatableCurrencyTableModel extends EditableCurrencyTableModel { public int getColumnCount() { return super.getColumnCount() + 1; } public Object getValueAt(int row, int column) { if (column == BUTTON_COLUMN) { return "Update"; } return super.getValueAt(row, column); } public Class getColumnClass(int column) { if (column == BUTTON_COLUMN) { return String.class; } return super.getColumnClass(column); } public String getColumnName(int column) { if (column == BUTTON_COLUMN) { return ""; } return super.getColumnName(column); } public boolean isCellEditable(int row, int column) { return column == BUTTON_COLUMN || super.isCellEditable(row, column); } public void setValueAt(Object value, int row, int column) { if (column == BUTTON_COLUMN) { // Button press - do whatever is needed to update the table source updateTable(value, row, column); return; } // Other columns - use superclass super.setValueAt(value, row, column); } // Used to implement the table update protected abstract void updateTable(Object value, int row, int column); protected static final int BUTTON_COLUMN = 4; } class BasicCellEditor implements CellEditor, PropertyChangeListener { public BasicCellEditor() { this.editor = null; } public BasicCellEditor(JComponent editor) { this.editor = editor; editor.addPropertyChangeListener(this); } public Object getCellEditorValue() { return null; } public boolean isCellEditable(EventObject evt) { editingEvent = evt; return true; } public boolean shouldSelectCell(EventObject evt) { return true; } public boolean stopCellEditing() { fireEditingStopped(); return true; } public void cancelCellEditing() { fireEditingCanceled(); } public void addCellEditorListener(CellEditorListener l) { listeners.add(CellEditorListener.class, l); } public void removeCellEditorListener(CellEditorListener l) { listeners.remove(CellEditorListener.class, l); } // Returns the editing component public JComponent getComponent() { return editor; } // Sets the editing component public void setComponent(JComponent comp) { editor = comp; } // Returns the event that triggered the edit public EventObject getEditingEvent() { return editingEvent; } // Method invoked when the editor is installed in the table. // Overridden in derived classes to take any convenient // action. public void editingStarted(EventObject event) { } protected void fireEditingStopped() { Object[] l = listeners.getListenerList(); for (int i = l.length - 2; i >= 0; i -= 2) { if (l[i] == CellEditorListener.class) { if (changeEvent == null) { changeEvent = new ChangeEvent(this); } ((CellEditorListener) l[i + 1]).editingStopped(changeEvent); } } } protected void fireEditingCanceled() { Object[] l = listeners.getListenerList(); for (int i = l.length - 2; i >= 0; i -= 2) { if (l[i] == CellEditorListener.class) { if (changeEvent == null) { changeEvent = new ChangeEvent(this); } ((CellEditorListener) l[i + 1]).editingCanceled(changeEvent); } } } // Implementation of the PropertyChangeListener interface public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("ancestor") && evt.getNewValue() != null) { // Added to table - notify the editor editingStarted(editingEvent); } } protected static JCheckBox checkBox = new JCheckBox(); protected static ChangeEvent changeEvent; protected JComponent editor; protected EventListenerList listeners = new EventListenerList(); protected EventObject editingEvent; } class ButtonEditor extends BasicCellEditor implements ActionListener, TableCellEditor { public ButtonEditor(JButton button) { super(button); button.addActionListener(this); } public void setForeground(Color foreground) { this.foreground = foreground; editor.setForeground(foreground); } public void setBackground(Color background) { this.background = background; editor.setBackground(background); } public void setFont(Font font) { this.font = font; editor.setFont(font); } public Object getCellEditorValue() { return value; } public void editingStarted(EventObject event) { // Edit starting - click the button if necessary if (!(event instanceof MouseEvent)) { // Keyboard event - click the button SwingUtilities.invokeLater(new Runnable() { public void run() { ((JButton) editor).doClick(); } }); } } public Component getTableCellEditorComponent(JTable tbl, Object value, boolean isSelected, int row, int column) { editor.setForeground(foreground != null ? foreground : tbl .getForeground()); editor.setBackground(background != null ? background : tbl .getBackground()); editor.setFont(font != null ? font : tbl.getFont()); this.value = value; setValue(value); return editor; } protected void setValue(Object value) { JButton button = (JButton) editor; if (value == null) { button.setText(""); button.setIcon(null); } else if (value instanceof Icon) { button.setText(""); button.setIcon((Icon) value); } else if (value instanceof DataWithIcon) { DataWithIcon d = (DataWithIcon) value; button.setText(d.toString()); button.setIcon(d.getIcon()); } else { button.setText(value.toString()); button.setIcon(null); } } public void actionPerformed(ActionEvent evt) { // Button pressed - stop the edit stopCellEditing(); } protected Object value; protected Color foreground; protected Color background; protected Font font; } class EditableCurrencyTableModel extends CurrencyTableModel { public boolean isCellEditable(int row, int column) { return column == OLD_RATE_COLUMN || column == NEW_RATE_COLUMN; } public void setValueAt(Object value, int row, int column) { try { if (column == OLD_RATE_COLUMN || column == NEW_RATE_COLUMN) { Double newObjectValue; // New value as an Object double newValue; // double, for validity checking if (value instanceof Number) { // Convert Number to Double newValue = ((Number) value).doubleValue(); newObjectValue = new Double(newValue); } else if (value instanceof String) { // Convert a String to a Double newObjectValue = new Double((String) value); newValue = newObjectValue.doubleValue(); } else { // Unrecognized - ignore return; } if (newValue > (double) 0.0) { // Store new value, but reject zero or negative values data[row][column] = newObjectValue; data[row][DIFF_COLUMN] = new Double( ((Double) data[row][NEW_RATE_COLUMN]).doubleValue() - ((Double) data[row][OLD_RATE_COLUMN]) .doubleValue()); fireTableRowsUpdated(row, row); } } } catch (NumberFormatException e) { // Ignore a badly-formatted number } } } class CurrencyTableModel extends AbstractTableModel { protected String[] columnNames = { "Currency", "Yesterday", "Today", "Change" }; // Constructor: calculate currency change to create the last column public CurrencyTableModel() { for (int i = 0; i < data.length; i++) { data[i][DIFF_COLUMN] = new Double( ((Double) data[i][NEW_RATE_COLUMN]).doubleValue() - ((Double) data[i][OLD_RATE_COLUMN]).doubleValue()); } } // Implementation of TableModel interface public int getRowCount() { return data.length; } public int getColumnCount() { return COLUMN_COUNT; } public Object getValueAt(int row, int column) { return data[row][column]; } public Class getColumnClass(int column) { return (data[0][column]).getClass(); } public String getColumnName(int column) { return columnNames[column]; } protected static final int OLD_RATE_COLUMN = 1; protected static final int NEW_RATE_COLUMN = 2; protected static final int DIFF_COLUMN = 3; protected static final int COLUMN_COUNT = 4; protected static final Class thisClass = CurrencyTableModel.class; protected Object[][] data = new Object[][] { { new DataWithIcon("Belgian Franc", new ImageIcon(thisClass .getResource("belgium.gif"))), new Double(37.6460110), new Double(37.6508921), null }, { new DataWithIcon("British Pound", new ImageIcon(thisClass .getResource("gb.gif"))), new Double(0.6213051), new Double(0.6104102), null }, { new DataWithIcon("Canadian Dollar", new ImageIcon(thisClass .getResource("canada.gif"))), new Double(1.4651209), new Double(1.5011104), null }, { new DataWithIcon("French Franc", new ImageIcon(thisClass .getResource("france.gif"))), new Double(6.1060001), new Double(6.0100101), null }, { new DataWithIcon("Italian Lire", new ImageIcon(thisClass .getResource("italy.gif"))), new Double(1181.3668977), new Double(1182.104), null }, { new DataWithIcon("German Mark", new ImageIcon(thisClass .getResource("germany.gif"))), new Double(1.8191804), new Double(1.8223421), null }, { new DataWithIcon("Japanese Yen", new ImageIcon(thisClass .getResource("japan.gif"))), new Double(141.0815412), new Double(121.0040432), null } }; } class ButtonRenderer extends JButton implements TableCellRenderer { public ButtonRenderer() { this.border = getBorder(); this.setOpaque(true); } public void setForeground(Color foreground) { this.foreground = foreground; super.setForeground(foreground); } public void setBackground(Color background) { this.background = background; super.setBackground(background); } public void setFont(Font font) { this.font = font; super.setFont(font); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Color cellForeground = foreground != null ? foreground : table .getForeground(); Color cellBackground = background != null ? background : table .getBackground(); setFont(font != null ? font : table.getFont()); if (hasFocus) { setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); if (table.isCellEditable(row, column)) { cellForeground = UIManager .getColor("Table.focusCellForeground"); cellBackground = UIManager .getColor("Table.focusCellBackground"); } } else { setBorder(border); } super.setForeground(cellForeground); super.setBackground(cellBackground); // Customize the component's appearance setValue(value); return this; } protected void setValue(Object value) { if (value == null) { setText(""); setIcon(null); } else if (value instanceof Icon) { setText(""); setIcon((Icon) value); } else if (value instanceof DataWithIcon) { DataWithIcon d = (DataWithIcon) value; setText(d.toString()); setIcon(d.getIcon()); } else { setText(value.toString()); setIcon(null); } } protected Color foreground; protected Color background; protected Font font; protected Border border; }