前几篇文章介绍了JTable的基本用法,本文实现一个简单的JTable,算是前文的一个总结,并造福供拷贝党们。
一、主要功能
1.数据的增删改;
2.渲染器:“Vegetarian”列存放布尔值,以checkBox形式显示;“Sport”列存放字符串,以comboBox形式显示;
3.编辑器:“Name”的编辑器实现一个按钮,按下时弹出对话框;
4.ToolTip:各列和各单元格均具有自己的ToolTip,且单元格ToolTip与其值相关;
5.事件:检测单元格值的变更,并输出旧值、新值和单元格坐标。
二、程序设计
本程序根据功能可分为6部分,以6个类来实现,分别是:
Gui.java:实现GUI,成员有:1个JTable,2个按钮;
MyJTable.java:继承自JTable,重载2个方法:getToolTipText和createDefaultTableHeader,分别实现单元格和表头的toolTip;
MyTableModel.java:继承自DefaultTableModel,重载1个方法:getColumnClass,实现布尔值的checkBox形式显示。表格的基本功能均已被DefaultTableModel类实现,直接使用就好。如果你还需要对单元格可访问性等细节进行精确控制,可以重载相关方法。
TableCellListener.java:实现对单元格数据变更的检测。这是通过表格的addPropertyChangeListener方法实现的,而不是基于tableModel的addTableModelListener方法。后者的不足之处在前文中已经分析。
ButtonEditor.java:实现一个基于按钮的编辑器,被按下时弹出对话框;
ButtonRenderer.java:实现一个渲染器,可定制单元格的配色。
三、程序代码
Gui.java
package DefaultTableModelDemo;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import JButtonTableExample.ButtonEditor;
import JButtonTableExample.ButtonRenderer; public class Gui extends JPanel { private Object[] tmpRow = {"tmpName", "tmpDescription"};
private MyJTable table;
private JButton addBtn;
private JButton delBtn;
private MyTableModel model ; public Gui() {
table = new MyJTable();
table.setPreferredScrollableViewportSize(new Dimension(500, 300));
table.setFillsViewportHeight(true); //Create the scroll pane and add the table to it.
JScrollPane scrollPane = new JScrollPane(table);
//scrollPane.setPreferredSize(new Dimension(500, 600));
//scrollPane.set
//Add the scroll pane to this panel.
add(scrollPane);
//set tableModel and data
model = new MyTableModel();
String[] columnNames = {"Name",
"Description",
"Sport",
"# of Years",
"Vegetarian"};
Object[][] data = {
{"Kathy", "Smith",
"Snowboarding", new Integer(5), new Boolean(false)},
{"John", "Doe",
"Rowing", new Integer(3), new Boolean(true)},
{"Sue", "Black",
"Knitting", new Integer(2), new Boolean(false)},
{"Jane", "White",
"Speed reading", new Integer(20), new Boolean(true)},
{"Joe", "Brown",
"Pool", new Integer(10), new Boolean(false)}
};
model.setDataVector(data, columnNames);
table.setModel(model);
//添加渲染器
table.getColumn("Name").setCellRenderer(new ButtonRenderer());
//添加编辑器
table.getColumn("Name").setCellEditor( new ButtonEditor());
//添加按钮
addBtn = new JButton("增加");
addBtn.addActionListener(new ActionListener() { @Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
model.addRow(tmpRow);
}
}); delBtn = new JButton("删除");
delBtn.addActionListener(new ActionListener() { @Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
int rowIndex = table.getSelectedRow();
if(rowIndex != -1)
model.removeRow(rowIndex);
}
}); add(addBtn);
add(delBtn); addDataChangeListener(); //设置列
setSportsColumn();
} private void setSportsColumn(){
String [] itmes = {"Snowboarding", "Rowing", "Knitting", "Speed reading", "Pool"};
JComboBox<String> comboBox = new JComboBox<String>(itmes);
DefaultTableCellRenderer renderer =
new DefaultTableCellRenderer();
renderer.setToolTipText("Click for combo box");
setColumn("Sport", comboBox, renderer);
TableColumn col = table.getColumn("Sport");
//setToolTipText("favorit sport is " + );
} public void setColumn(String colName, Object editor, Object renderer) {
int index = table.getColumnModel().getColumnIndex(colName);
TableColumn modeColumn = table.getColumnModel().getColumn(index);
if (editor instanceof JComponent) {
setEditor(modeColumn, (JComponent)editor);
}
else if (editor instanceof DefaultCellEditor) {
modeColumn.setCellEditor((DefaultCellEditor)editor);
} if (renderer instanceof DefaultTableCellRenderer) {
modeColumn.setCellRenderer((DefaultTableCellRenderer)renderer);
}
else if (renderer instanceof ButtonRenderer) {
modeColumn.setCellRenderer((ButtonRenderer)renderer);
}
} protected void setEditor(TableColumn column, JComponent component){
if(component instanceof JTextField )
column.setCellEditor(new DefaultCellEditor((JTextField) component));
else if(component instanceof JComboBox )
column.setCellEditor(new DefaultCellEditor((JComboBox<String>) component));
else if(component instanceof JCheckBox )
column.setCellEditor(new DefaultCellEditor((JCheckBox) component));
} private void addDataChangeListener(){
//检测单元格数据变更
Action action = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
TableCellListener tcl = (TableCellListener)e.getSource();
int row = tcl.getRow();
int col = tcl.getColumn();
Object oldValue = tcl.getOldValue();
//if(oldValue == null)
//oldValue = "";
Object newValue = tcl.getNewValue();
//if(newValue == null)
//newValue = "";
System.out.printf("cell changed at [%d,%d] : %s -> %s%n",row, col, oldValue, newValue);
}
};
@SuppressWarnings("unused")
TableCellListener tcl1 = new TableCellListener(table, action);
System.out.printf("cell changed%n");
} private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Gui");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Create and set up the content pane.
Gui newContentPane = new Gui();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane); //Display the window.
frame.pack();
frame.setVisible(true);
} public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
MyTableModel.java
package DefaultTableModelDemo; import javax.swing.table.DefaultTableModel; public class MyTableModel extends DefaultTableModel{ @Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex == 4)
return Boolean.class;
return super.getColumnClass(columnIndex);
}
}
MyJTable.java
package DefaultTableModelDemo; import java.awt.event.MouseEvent; import javax.swing.JTable;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel; public class MyJTable extends JTable{ protected String[] columnToolTips = {null,
null,
"The person's favorite sport to participate in is : ",
"The number of years the person has played the sportis : ",
"If checked, the person eats no meat"}; //Implement table cell tool tips.
public String getToolTipText(MouseEvent e) {
String tip = null;
java.awt.Point p = e.getPoint();
int rowIndex = rowAtPoint(p);
int colIndex = columnAtPoint(p);
int realColumnIndex = convertColumnIndexToModel(colIndex);
if(rowIndex < 0)
{
//System.out.printf("abnormal rowIndex: %n", rowIndex);
return null;
} if (realColumnIndex == 2) { //Sport column
tip = columnToolTips[2]
+ getValueAt(rowIndex, colIndex);
}
else if (realColumnIndex == 3) { //Years column
tip = columnToolTips[3] + getValueAt(rowIndex, colIndex); }else if (realColumnIndex == 4) { //Veggie column
TableModel model = getModel();
String firstName = (String)model.getValueAt(rowIndex,0);
String lastName = (String)model.getValueAt(rowIndex,1);
Boolean veggie = (Boolean)model.getValueAt(rowIndex,4);
if (Boolean.TRUE.equals(veggie)) {
tip = firstName + " " + lastName
+ " is a vegetarian";
} else {
tip = firstName + " " + lastName
+ " is not a vegetarian";
}
} else {
//You can omit this part if you know you don't
//have any renderers that supply their own tool
//tips.
tip = super.getToolTipText(e);
}
return tip;
} //Implement table header tool tips.
protected JTableHeader createDefaultTableHeader() {
return new JTableHeader(columnModel) {
public String getToolTipText(MouseEvent e) {
String tip = null;
java.awt.Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex =
columnModel.getColumn(index).getModelIndex();
return columnToolTips[realIndex];
}
};
} }
TableCellListener.java
package DefaultTableModelDemo;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*; /*
* This class listens for changes made to the data in the table via the
* TableCellEditor. When editing is started, the value of the cell is saved
* When editing is stopped the new value is saved. When the oold and new
* values are different, then the provided Action is invoked.
*
* The source of the Action is a TableCellListener instance.
*/
public class TableCellListener implements PropertyChangeListener, Runnable
{
private JTable table;
private Action action; private int row;
private int column;
private Object oldValue;
private Object newValue; /**
* Create a TableCellListener.
*
* @param table the table to be monitored for data changes
* @param action the Action to invoke when cell data is changed
*/ public TableCellListener(JTable table, Action action)
{
this.table = table;
this.action = action;
this.table.addPropertyChangeListener( this );
} /**
* Create a TableCellListener with a copy of all the data relevant to
* the change of data for a given cell.
*
* @param row the row of the changed cell
* @param column the column of the changed cell
* @param oldValue the old data of the changed cell
* @param newValue the new data of the changed cell
*/
private TableCellListener(JTable table, int row, int column, Object oldValue, Object newValue)
{
this.table = table;
this.row = row;
this.column = column;
this.oldValue = oldValue;
this.newValue = newValue;
} /**
* Get the column that was last edited
*
* @return the column that was edited
*/
public int getColumn()
{
return column;
} /**
* Get the new value in the cell
*
* @return the new value in the cell
*/
public Object getNewValue()
{
return newValue;
} /**
* Get the old value of the cell
*
* @return the old value of the cell
*/
public Object getOldValue()
{
return oldValue;
} /**
* Get the row that was last edited
*
* @return the row that was edited
*/
public int getRow()
{
return row;
} /**
* Get the table of the cell that was changed
*
* @return the table of the cell that was changed
*/
public JTable getTable()
{
return table;
}
//
// Implement the PropertyChangeListener interface
//
@Override
public void propertyChange(PropertyChangeEvent e)
{
// A cell has started/stopped editing if ("tableCellEditor".equals(e.getPropertyName()))
{
if (table.isEditing()){
//System.out.printf("tableCellEditor is editing..%n");
processEditingStarted();
}
else{
//System.out.printf("tableCellEditor editing stopped..%n");
processEditingStopped();
} }
} /*
* Save information of the cell about to be edited
*/
private void processEditingStarted()
{
// The invokeLater is necessary because the editing row and editing
// column of the table have not been set when the "tableCellEditor"
// PropertyChangeEvent is fired.
// This results in the "run" method being invoked SwingUtilities.invokeLater( this );
}
/*
* See above.
*/
@Override
public void run()
{
row = table.convertRowIndexToModel( table.getEditingRow() );
column = table.convertColumnIndexToModel( table.getEditingColumn() );
oldValue = table.getModel().getValueAt(row, column);
//这里应对oldValue为null的情况做处理,否则将导致原值与新值均为空时仍被视为值改变
if(oldValue == null)
oldValue = "";
newValue = null;
} /*
* Update the Cell history when necessary
*/
private void processEditingStopped()
{
newValue = table.getModel().getValueAt(row, column);
//这里应对newValue为null的情况做处理,否则后面会抛出异常
if(newValue == null)
newValue = "";
// The data has changed, invoke the supplied Action
if (! newValue.equals(oldValue))
{
// Make a copy of the data in case another cell starts editing
// while processing this change TableCellListener tcl = new TableCellListener(
getTable(), getRow(), getColumn(), getOldValue(), getNewValue()); ActionEvent event = new ActionEvent(
tcl,
ActionEvent.ACTION_PERFORMED,
"");
action.actionPerformed(event);
}
}
}
ButtonEditor.java
package JButtonTableExample; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JTable; public class ButtonEditor extends DefaultCellEditor {
protected JButton button;//represent the cellEditorComponent
private String cellValue;//保存cellEditorValue public ButtonEditor() {
super(new JCheckBox());
button = new JButton();
button.setOpaque(true);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(button, cellValue + ": Ouch!");
//刷新渲染器
fireEditingStopped();
}
});
} public JComponent getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
//value 源于单元格数值
cellValue = (value == null) ? "" : value.toString();
return button;
} public Object getCellEditorValue() {
return new String(cellValue);
}
}
ButtonRenderer.java
package JButtonTableExample; import java.awt.Color;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer; public class ButtonRenderer extends JButton implements TableCellRenderer { public JComponent getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
//value 源于editor
String text = (value == null) ? "" : value.toString();
//按钮文字
setText(text);
//单元格提示
setToolTipText(text);
//背景色
setBackground(Color.BLACK);
//前景色
setForeground(Color.green);
return this;
}
}
运行效果如下: