前几篇文章介绍了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; } }
运行效果如下: