|
|
|
Training Index
Writing Advanced Applications
[<<BACK]
[CONTENTS]
[NEXT>>] |
| Value | Layer Name | Component Types |
|---|---|---|
| -3000 | DEFAULT_FRAME_LAYER |
JMenubar |
| 0 | DEFAULT_LAYER |
JButton, JTable, .. |
PALETTE_LAYER |
Floating components such as JToolBar |
|
MODAL_LAYER |
Modal Dialogs | |
| 400 | DRAG_LAYER |
Drag-and-drop over all layers |
Within these general depth bands, components can be further arranged
with another numbering system to order the components in a
particular band, but this system reverses the numbering priority. For example,
in a specific band such as DEFAULT_LAYER, components with a value
of 0 appear in front of others in that band; whereas, components with a higher
number or -1 appear behind them. The highest number in
this scheme is the number of components minus 1, so one way to visualize
it is a vector of components that steps through painting the components
with a higher number first finishing with the one at position 0.
For example, the following code adds a JButton to the default
layer and specifies that it appear in front of the other components in that
same layer:
JButton enterButton = new JButton("Enter");
layeredPane.add(enterButton,
JLayeredPane.Default_Layer, 0);
You can achieve the same effect by calling the LayeredPane.moveToFont
method within a layer or using the LayeredPane.setLayer method
to move to a different layer.
JContentPane manages adding components to heavyweight containers.
So, you have to call the getContentPane method to add a component to
the ContentPane of the RootPane. By default, a
ContentPane is initialized with a BorderLayout
layout manager. There are two ways to change the layout manager. You can
call the setLayout method like this:
getContentPane()).setLayout(new BoxLayout())Or you can replace the default
ContentPane with your own
ContentPane, such as a JPanel, like this:
JPanel pane= new JPanel(); pane.setLayout(new BoxLayout()); setContentPane(pane);
GlassPane is usually completely transparent and just
acts as a sheet of glass in front of the components. You can implement
your own GlassPane by using a component like JPanel
and installing it as the GlassPane by calling the
setGlassPane method. The RootPane is configured with
a GlassPane that can be retrieved by calling getGlassPane.
One way to use a GlassPane is to implement a component
that invisibly handles all mouse and keyboard events, effectively
blocking user input until an event completes. The GlassPane
can block the events, but currently the cursor will not return
to its default state if you have set the cursor to be a busy cursor
in the GlassPane. An additional mouse event is required for
the refresh.
MyGlassPane glassPane = new MyGlassPane();
setGlassPane(glassPane);
setGlassPane.setVisible(true); //before worker thread
..
setGlassPane.setVisible(false); //after worker thread
private class MyGlassPane extends JPanel {
public MyGlassPane() {
addKeyListener(new KeyAdapter() { });
addMouseListener(new MouseAdapter() { });
super.setCursor(
Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
}
AuctionClient GUI.
At a foundational level, the TableModel interface and its two
implementations AbstractTableModel and DefaultTableModel
provide the most basic means for storage, retrieval and modification of the
underlying data.
The TableModel is responsible for defining and categorizing the
data by its class. It also determines if the data can be edited and how
the data is grouped into columns and rows.
It is important to note, however, that while the TableModel
interface is used
most often in the construction of a JTable, it is not
fundamentally tied to
their display. Implementations could just as easily form the basis of a
spreadsheet component, or even a non-GUI class that calls for the organization
of data in tabular format.
The ResultsModel class is at the heart of the
AuctionClient tables.
It defines a dynamic data set, dictates whether class users can
edit the data through its ResultsModel.isCellEditable method,
and provides the update method to keep the data current.
The model underlies the scrolling and fixed tables, and lets
modifications to be reflected in each view.
At a higher level, and representing an intermediate layer between data and its
graphical representation, is the TableColumnModel. At this level
the data is grouped by column in anticipation of its ultimate display in the
table. The visibility and size of these columns, their headers, and the
component types of their cell renderers and editors are all managed by
the TableColumnModel class.
For example, freezing the left-most columns in the AuctionClient
GUI is possible because column data is easily exchanged among multiple
TableColumnModel and JTable
objects. This translates to the fixedTable and scrollTable
objects of the AuctionClient program.
Higher still lie the various renderers, editors, and header components whose
combination define the look and organization of the JTable component.
This level is where the fundamental layout and display decisions of the
JTable are made.
The creation of the inner classes CustomRenderer and
CustomButtonRenderer within the AuctionClient application
allows users of those classes to redefine the components upon which the
appearance of table cells are based. Likewise, the CustomButtonEditor
class takes the place of the table's default editor.
In true object-oriented fashion, the default editors and renderers are easily
replaced, affecting neither the data they represent nor the function of the
component in which they reside.
Finally, the various component user interfaces are responsible for the ultimate
appearance of the JTable. It is here the look-and-feel-specific
representation of the AuctionClient tables and their data are
rendered in final form to the user. The end result is that adding a Project
Swing front-end to existing services requires little additional code.
In fact, coding the model is one of the easier tasks in building a Project
Swing application.
JTable class has an associated DefaultTableModel
class that internally uses a Vector of vectors to store data internally. The
data for each row is stored in a singl Vector object while another
Vector object stores each of those rows as its constituent elements.
The DefaultTableModel object can be initialized with data in
several different ways. This code shows the DefaultTableModel
created with a two-dimensional array and a second array representing column
headings. The DefaultTableModel in turn converts the Object
arrays into the appropriate Vector objects:
Object[][] data = new Object[][]{ {"row 1 col1",
"Row 1 col2" },
{"row 2 col 1",
"row 2 col 2"}
};
Object[] headers = new Object[] {"first header",
"second header"};
DefaultTableModel model = new DefaultTableModel(data,
headers);
table = new JTable(model);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
Creating a custom table model is nearly as easy as using
DefaultTableModel, and requires little additional coding.
You can implement a table model by implementing a method to return the number
of entries in the model, and a method to retrieve an element at a specific
position in that model. For example, the JTable model can be
implemented from javax.swing.table.AbstractTableModel by
implementing the methods getColumnCount, getRowCount
and getValueAt as shown here:
final Object[][] data = new Object[][]{ {
"row 1 col1",
"row 1 col2" },
{"row 2 col 1",
"row 2 col 2"} };
final Object[] headers = new Object[] {
"first header",
"second header"};
TableModel model = new AbstractTableModel(){
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return (String)headers[col];
}
public Object getValueAt(int row,int col) {
return data[row][col];
}
};
table = new JTable(model);
table.setAutoResizeMode(
JTable.AUTO_RESIZE_OFF);
This table is read-only and its data values are already known. In fact,
the data is even declared final so it can be retrieved
by the inner TableModel class. This is not normally the situation
when working with live data.
You can create an editable table by by adding the isCellEditable
verification method, which is used by the default cell editor, and the
AbstractTableModel method for setting a value at a position.
Up until this change, the AbstractTableModel has been handling
the repainting and resizing of the table by firing different table changed
events. Because the AbtractTableModel does not
know that something has occured to the table data, you need to inform
it by calling the fireTableCellUpdated method. The following
lines are added to the AbstractTableModel inner class to
allow editing of the data:
public void setValueAt (Object value,
int row, int col) {
data[row][col] = value;
fireTableCellUpdated (row, col);
}
public boolean isCellEditable(int row,
int col) {
return true;
}
The base table model in this example implements the
AbstractTableModel class. Its update method
dynamically populates the table data from a call to the database. It
sends an event that the table has been updated by calling the
fireTableStructureChanged method to indicate the
number of rows or columns in the table have changed.
package auction;
import javax.swing.table.AbstractTableModel;
import javax.swing.event.TableModelEvent;
import java.text.NumberFormat;
import java.util.*;
import java.awt.*;
public class ResultsModel extends AbstractTableModel{
String[] columnNames={};
Vector rows = new Vector();
public String getColumnName(int column) {
if (columnNames[column] != null) {
return columnNames[column];
} else {
return "";
}
}
public boolean isCellEditable(int row, int column){
return false;
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return rows.size();
}
public Object getValueAt(int row, int column){
Vector tmprow = (Vector)rows.elementAt(row);
return tmprow.elementAt(column);
}
public void update(Enumeration enum) {
try {
columnNames = new String[5];
columnNames[0]=new String("Auction Id #");
columnNames[1]=new String("Description");
columnNames[2]=new String("High Bid");
columnNames[3]=new String("# of bids");
columnNames[4]=new String("End Date");
while((enum !=null) &&
(enum.hasMoreElements())) {
while(enum.hasMoreElements()) {
AuctionItem auctionItem=(
AuctionItem)enum.nextElement();
Vector items=new Vector();
items.addElement(new Integer(
auctionItem.getId()));
items.addElement(
auctionItem.getSummary());
int bidcount= auctionItem.getBidCount();
if(bidcount >0) {
items.addElement(
NumberFormat.getCurrencyInstance().
format(auctionItem.getHighBid()));
} else {
items.addElement("-");
}
items.addElement(new Integer(bidcount));
items.addElement(auctionItem.getEndDate());
rows.addElement(items);
}
}
fireTableStructureChanged();
} catch (Exception e) {
System.out.println("Exception e"+e);
}
}
}
The table is created from the ResultsModel model. Then,
the first table column is removed from that table and added to a new
table. Because there are now two tables, the only way the selections
can be kept in sync is to use a ListSelectionModel object
to set the selection on the table row in the other tables that were not
selected by calling the setRowSelectionInterval method.
The full example can be found in the AuctionClient.java source file:
private void listAllItems() throws IOException{
ResultsModel rm=new ResultsModel();
if (!standaloneMode) {
try {
BidderHome bhome=(BidderHome)
ctx.lookup("bidder");
Bidder bid=bhome.create();
Enumeration enum=
(Enumeration)bid.getItemList();
if (enum != null) {
rm.update(enum);
}
} catch (Exception e) {
System.out.println(
"AuctionServlet <list>:"+e);
}
} else {
TestData td= new TestData();
rm.update(td.results());
}
scrollTable=new JTable(rm);
adjustColumnWidth(scrollTable.getColumn(
"End Date"), 150);
adjustColumnWidth(scrollTable.getColumn(
"Description"), 120);
scrollColumnModel = scrollTable.getColumnModel();
fixedColumnModel = new DefaultTableColumnModel();
TableColumn col = scrollColumnModel.getColumn(0);
scrollColumnModel.removeColumn(col);
fixedColumnModel.addColumn(col);
fixedTable = new JTable(rm,fixedColumnModel);
fixedTable.setRowHeight(scrollTable.getRowHeight());
headers = new JViewport();
ListSelectionModel fixedSelection =
fixedTable.getSelectionModel();
fixedSelection.addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
ListSelectionModel lsm = (
ListSelectionModel)e.getSource();
if (!lsm.isSelectionEmpty()) {
setScrollableRow();
}
}
});
ListSelectionModel scrollSelection =
scrollTable.getSelectionModel();
scrollSelection.addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
ListSelectionModel lsm =
(ListSelectionModel)e.getSource();
if (!lsm.isSelectionEmpty()) {
setFixedRow();
}
}
});
CustomRenderer custom = new CustomRenderer();
custom.setHorizontalAlignment(JLabel.CENTER);
scrollColumnModel.getColumn(2).setCellRenderer(
custom);
scrollColumnModel.getColumn(3).setCellRenderer(
new CustomButtonRenderer());
CustomButtonEditor customEdit=new
CustomButtonEditor(frame);
scrollColumnModel.getColumn(3).setCellEditor(
customEdit);
headers.add(scrollTable.getTableHeader());
JPanel topPanel = new JPanel();
topPanel.setLayout(new BoxLayout(topPanel,
BoxLayout.X_AXIS));
adjustColumnWidth(
fixedColumnModel.getColumn(0), 100);
JTableHeader fixedHeader=
fixedTable.getTableHeader();
fixedHeader.setAlignmentY(Component.TOP_ALIGNMENT);
topPanel.add(fixedHeader);
topPanel.add(Box.createRigidArea(
new Dimension(2, 0)));
topPanel.setPreferredSize(new Dimension(400, 40));
JPanel headerPanel = new JPanel();
headerPanel.setAlignmentY(Component.TOP_ALIGNMENT);
headerPanel.setLayout(new BorderLayout());
JScrollPane scrollpane = new JScrollPane();
scrollBar = scrollpane.getHorizontalScrollBar();
headerPanel.add(headers, "North");
headerPanel.add(scrollBar, "South");
topPanel.add(headerPanel);
scrollTable.setPreferredScrollableViewportSize(
new Dimension(300,180));
fixedTable.setPreferredScrollableViewportSize(
new Dimension(100,180));
fixedTable.setPreferredSize(
new Dimension(100,180));
innerPort = new JViewport();
innerPort.setView(scrollTable);
scrollpane.setViewport(innerPort);
scrollBar.getModel().addChangeListener(
new ChangeListener() {
public void stateChanged(ChangeEvent e) {
Point q = headers.getViewPosition();
Point p = innerPort.getViewPosition();
int val = scrollBar.getModel().getValue();
p.x = val;
q.x = val;
headers.setViewPosition(p);
headers.repaint(headers.getViewRect());
innerPort.setViewPosition(p);
innerPort.repaint(innerPort.getViewRect());
}
});
scrollTable.getTableHeader(
).setUpdateTableInRealTime(
false);
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(
bottomPanel, BoxLayout.X_AXIS));
fixedTable.setAlignmentY(Component.TOP_ALIGNMENT);
bottomPanel.add(fixedTable);
bottomPanel.add(Box.createRigidArea(
new Dimension(2, 0)));
innerPort.setAlignmentY(Component.TOP_ALIGNMENT);
bottomPanel.add(innerPort);
bottomPanel.add(Box.createRigidArea(
new Dimension(2, 0)));
scrollPane= new JScrollPane(bottomPanel,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JViewport outerPort = new JViewport();
outerPort.add(bottomPanel);
scrollPane.setColumnHeaderView(topPanel);
scrollPane.setViewport(outerPort);
scrollTable.setAutoResizeMode(
JTable.AUTO_RESIZE_OFF);
frame.getContentPane().add(scrollPane);
scrollTable.validate();
frame.setSize(450,200);
}
void setFixedRow() {
int index=scrollTable.getSelectedRow();
fixedTable.setRowSelectionInterval(index, index);
}
void setScrollableRow() {
int index=fixedTable.getSelectedRow();
scrollTable.setRowSelectionInterval(index, index);
}
void adjustColumnWidth(TableColumn c, int size) {
c.setPreferredWidth(size);
c.setMaxWidth(size);
c.setMinWidth(size);
}
JList component displays a vertical list of data elements
and uses a ListModel to hold and manipulate the data. It
also uses a ListSelectionModel object to enable selection and
subsequent retrieval of elements in the list.
Default implementations of the AbstractListModel and
AbstractListSelectionModel classes are provided in the Project
Swing API in the form of the DefaultListModel and
DefaultListSelectionModel classes. If you use these two default
models and the default cell renderer,
you get a list that displays model elements by calling the
toString method on each object. The list uses the
MULTIPLE_INTERVAL_SELECTION list selection model to select
each element from the list.
Three selection modes are available to DefaultListSelectionModel:
SINGLE_SELECTION, where only one item is selected at a
time; SINGLE_INTERVAL_SELECTION in which a range of sequential
items can be selected; and MULTIPLE_INTERVAL_SELECTION, which
allows any or all elements to be selected. The
selection mode can be changed by calling the setSelectionMode
method in the JList class.
public SimpleList() {
JList list;
DefaultListModel deflist;
deflist= new DefaultListModel();
deflist.addElement("element 1");
deflist.addElement("element 2");
list = new JList(deflist);
JScrollPane scroll = new JScrollPane(list);
getContentPane().add(scroll, BorderLayout.CENTER);
}
JTree class models and displays a vertical list
of elements or nodes arranged in a tree-based hierarchy.
A JTree object has one root node and one or more child nodes,
which can contain further child nodes. Each parent node can be expanded
to show all its children similiar to directory trees familiar to Windows
users.
Like the JList and JTable components,
the JTree consists of more than one model. The selection model
is similiar to the one detailed for the JList model. The
selection modes have the following slightly different names:
SINGLE_TREE_SELECTION, DISCONTIGUOUS_TREE_SELECTION,
and CONTIGUOUS_TREE_SELECTION.
While DefaultTreeModel maintains the data in the tree and
is responsible for adding and removing nodes, it is the
DefaultTreeMutableTreeNode class that defines the methods
used for node traversal. The DefaultTreeModel
is often used to implement custom models because there is no
AbstractTreeModel in the JTree package. However,
if you use custom objects, you must implement TreeModel.
This code example creates a JTree using the
DefaultTreeModel.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
public class SimpleTree extends JFrame {
public SimpleTree() {
String[] treelabels = {
"All Auctions",
"Closed Auction",
"Open Auctions"};
Integer[] closedItems = { new Integer(500144),
new Integer(500146),
new Integer(500147)};
Integer[] openItems = { new Integer(500148),
new Integer(500149)};
DefaultMutableTreeNode[] nodes = new
DefaultMutableTreeNode[treelabels.length];
DefaultMutableTreeNode[] closednodes = new
DefaultMutableTreeNode[closedItems.length];
DefaultMutableTreeNode[] opennodes = new
DefaultMutableTreeNode[openItems.length];
for (int i=0; i < treelabels.length; i++) {
nodes[i] = new
DefaultMutableTreeNode(treelabels[i]);
}
nodes[0].add(nodes[1]);
nodes[0].add(nodes[2]);
for (int i=0; i < closedItems.length; i++) {
closednodes[i] = new
DefaultMutableTreeNode(closedItems[i]);
nodes[1].add(closednodes[i]);
}
for (int i=0; i < openItems.length; i++) {
opennodes[i] = new
DefaultMutableTreeNode(openItems[i]);
nodes[2].add(opennodes[i]);
}
DefaultTreeModel model=new
DefaultTreeModel(nodes[0]);
JTree tree = new JTree(model);
JScrollPane scroll = new JScrollPane(tree);
getContentPane().add(scroll, BorderLayout.CENTER);
}
public static void main(String[] args) {
SimpleTree frame = new SimpleTree();
frame.addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
System.exit(0);
}
});
frame.setVisible(true);
frame.pack();
frame.setSize(150,150);
}
}
The toString method is used to retrieve the value for the
Integer objects in the tree. And although the
DefaultTreeModel is used to maintain the data
in the tree and to add or remove nodes, the DefaultMutableTreeNode
class defines the methods used to traverse through the nodes in the tree.
A primitive search of the nodes in a JTree is accomplished
with the depthFirstEnumeration method, which is the same as
the postorderEnumeration method and works its
way from the end points of the tree first. Or you can call the
preorderEnumeration method, the reverse of the
postorderEnumeration method, which starts from the root and descends
each tree in turn. Or you can call the breadthFirstEnumeration
method, which starts from the root and visits all the child nodes in one level
before visiting the child nodes at a lower depth.
The following code expands the parent node if it contains a child node that
matches the search field entered. It uses a call to
Enumeration e = nodes[0].depthFirstEnumeration(); to return a
list of all the nodes in the tree. Once it has found a match, it builds the
TreePath from the root node to the node that matched the search to
pass to the makeVisible method in the JTree class
that ensures the node is expanded in the tree.
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
public class SimpleSearchTree extends JFrame {
JPanel findPanel;
JTextField findField;
JTree tree;
JButton findButton;
DefaultMutableTreeNode[] nodes;
public SimpleSearchTree() {
String[] treelabels = { "All Auctions",
"Closed Auction",
"Open Auctions" };
Integer[] closedItems = { new Integer(500144),
new Integer(500146),
new Integer(500147) };
Integer[] openItems ={ new Integer(500148),
new Integer(500149)};
nodes = new
DefaultMutableTreeNode[treelabels.length];
DefaultMutableTreeNode[] closednodes = new
DefaultMutableTreeNode[closedItems.length];
DefaultMutableTreeNode[] opennodes = new
DefaultMutableTreeNode[openItems.length];
for (int i=0; i < treelabels.length; i++) {
nodes[i] = new
DefaultMutableTreeNode(treelabels[i]);
}
nodes[0].add(nodes[1]);
nodes[0].add(nodes[2]);
for (int i=0; i < closedItems.length; i++) {
closednodes[i] = new
DefaultMutableTreeNode(closedItems[i]);
nodes[1].add(closednodes[i]);
}
for (int i=0; i < openItems.length; i++) {
opennodes[i] = new DefaultMutableTreeNode(
openItems[i]);
nodes[2].add(opennodes[i]);
}
DefaultTreeModel model=new
DefaultTreeModel(nodes[0]);
tree = new JTree(model);
JScrollPane scroll = new JScrollPane(tree);
getContentPane().add(scroll, BorderLayout.CENTER);
findPanel= new JPanel();
findField= new JTextField(10);
findButton= new JButton("find");
findButton.addActionListener (new ActionListener() {
public void actionPerformed (ActionEvent e) {
String field=findField.getText();
if (field != null) {
findNode(findField.getText());
} else {
return;
}
}
});
findPanel.add(findField);
findPanel.add(findButton);
getContentPane().add(findPanel, BorderLayout.SOUTH);
}
public void findNode(String field) {
Enumeration e = nodes[0].depthFirstEnumeration();
Object currNode;
while (e.hasMoreElements()) {
currNode = e.nextElement();
if (currNode.toString().equals(field)) {
TreePath path=new TreePath(((
DefaultMutableTreeNode)currNode).getPath());
tree.makeVisible(path);
tree.setSelectionRow(tree.getRowForPath(path));
return;
}
}
}
public static void main(String[] args) {
SimpleSearchTree frame = new SimpleSearchTree();
frame.addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
System.exit(0);
}
});
frame.setVisible(true);
frame.pack();
frame.setSize(300,150);
}
}
JTree, JTable and JList are
probably the most common models you will want to customize. But you
can use models such as SingleSelectionModel for general
data manipulation. The SingleSelectionModel class lets you
specify how data is selected in a component.
JLabel and displays a
String representation of the data element.
A simple custom cell renderer can extend the DefaultXXXCellRenderer
class to provide additional customization in the getXXXCellRenderer.
The DefaultTableCellRenderer and DefaultTreeCellRenderer
Components both use a JLabel to render the cell.
This means any customization that can be applied to a JLabel
can also be used in the JTable or JTree cell.
For example, the following renderer sets the background color of the component if the auction item has received a high number of bids:
class CustomRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(
JTable table,Object value,
boolean isSelected,
boolean hasFocus,
int row, int column) {
Component comp =
super.getTableCellRendererComponent(
table,value,isSelected,hasFocus,
row,column);
JLabel label = (JLabel)comp;
if(((Integer)value).intValue() >= 30) {
label.setIcon(new ImageIcon("Hot.gif"));
} else {
label.setIcon(new ImageIcon("Normal.gif"));
}
return label;
}
}
The renderer is set on a column like this:
CustomRenderer custom = new CustomRenderer();
custom.setHorizontalAlignment(JLabel.CENTER);
scrollColumnModel.getColumn(2).setCellRenderer(
custom);
If the component being displayed inside the JTable column requires
more functionality than is available using a JLabel, you can
create your own TableCellRenderer. This next code
example uses a JButton as the renderer cell.
class CustomButtonRenderer extends JButton
implements TableCellRenderer {
public CustomButtonRenderer() {
setOpaque(true);
}
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected,
boolean hasFocus, int row,
int column) {
if (isSelected) {
((JButton)value).setForeground(
table.getSelectionForeground());
((JButton)value).setBackground(
table.getSelectionBackground());
} else {
((JButton)value).setForeground(table.getForeground());
((JButton)value).setBackground(table.getBackground());
}
return (JButton)value;
}
}
Like the default JLabel cell renderer, this class relies on
an underlying component (in this case JButton) to do the painting.
Selection of the cell toggles the button colors. As before, the cell renderer
is secured to the appropriate column of the auction table with the
setCellRenderer method:
scrollColumnModel.getColumn(3).setCellRenderer( new CustomButtonRenderer());Alternately, all
JButton components can be configured to
use the CustomButtonRenderer in the table with a call to
setDefaultRenderer as follows:
table.setDefaultRenderer( JButton.class, new CustomButtonRenderer());
JTable or JTree component, you can also
configure how an editable cell responds to edits. One difference between
using cell editors and cell renderers is there is a
DefaultCellEditor for all components, but
no DefaultTableCellEditor for table cells.
While separate renderers exist for JTree and JTable,
a single DefaultCellEditor class implements both the
TableCellEditor and TreeCellEditor interfaces.
However, the DefaultCellEditor class has constructors for only
the JComboBox, JCheckBox, and JTextField
components. The JButton class does not map to any of these
constructors so a dummy JCheckBox is created to satisfy the
requirements of the DefaultCellEditor class.
This next example uses a custom button editor that displays the number
of days left in the auction when the button is double clicked. The
double click to trigger the action is specified by setting the value
clickCountToStart to two. An exact copy of the
getTableCellEditorComponent method paints the button in edit
mode. A JDialog component that displays the number
of days left appears when the getCellEditorValue method
is called.
The value for the number of days left is calculated by moving the current
calendar date towards the end date. The Calendar class does
not have a method that expresses a difference in two dates in anything
other than the milliseconds between those two dates.
class CustomButtonEditor extends DefaultCellEditor {
final JButton mybutton;
JFrame frame;
CustomButtonEditor(JFrame frame) {
super(new JCheckBox());
mybutton = new JButton();
this.editorComponent = mybutton;
this.clickCountToStart = 2;
this.frame=frame;
mybutton.setOpaque(true);
mybutton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
protected void fireEditingStopped() {
super.fireEditingStopped();
}
public Object getCellEditorValue() {
JDialog jd= new JDialog(frame, "Time left");
Calendar today=Calendar.getInstance();
Calendar end=Calendar.getInstance();
SimpleDateFormat in=new SimpleDateFormat("yyyy-MM-dd");
try {
end.setTime(in.parse(mybutton.getText()));
} catch (Exception e){
System.out.println("Error in date"+mybutton.getText()+e);
}
int days = 0;
while(today.before(end)) {
today.roll(Calendar.DATE,true);
days++;
}
jd.setSize(200,100);
if (today.after(end)) {
jd.getContentPane().add(new JLabel("Auction completed"));
} else {
jd.getContentPane().add(new JLabel("Days left="+days));
}
jd.setVisible(true);
return new String(mybutton.getText());
}
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected,
int row, int column) {
((JButton) editorComponent).setText(((
JButton)value).getText());
if (isSelected) {
((JButton) editorComponent).setForeground(
table.getSelectionForeground());
((JButton) editorComponent).setBackground(
table.getSelectionBackground());
} else {
((JButton) editorComponent).setForeground(
table.getForeground());
((JButton) editorComponent).setBackground(
table.getBackground());
}
return editorComponent;
}
}
SwingUtilities class that are used to add some control
over the event queue. The two new event handling methods are
invokeLater and invokeAndWait.
The invokeAndWait
method waits for the event to be processed in the event queue.
These methods are often used to request focus on a component after
another event has occurred that might affect the component focus. You
can return the focus by calling the invokeLater method and
passing a Thread:
JButton button =new JButton();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
button.requestFocus();
}
});
JTable and in areas such as
scrolling. Add to this the Java HotSpotTM
Performance Engine, which greatly reduces the cost of object creation,
and Project Swing can boast its best performance to date.
However, as seen in the Analyze a Program section in the Performance chapter, a simple 700x300 table requires nearly half a megabyte of memory when double buffered. The creation of ten tables would probably require swapping memory to disk, severly affecting performance on low-end machines.
[TOP]