|
|
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]