好久没有玩Swing了,算是练习英语,参考Sun公司官方给出的Java Tutorial的教程,来回顾一下JTable的用法,也希望大神来拍砖!
JTable顾名思义就是一个将数据以表格显示的组件,但是需要特别注意的是:因为Java Swing采用了MVC的设计,所以JTable不是用来存放数据的,它只是用来作为视图显示,而真正用来存储和维护数据的是TableModel这个接口的实现类。
从上面的图我们可以看出几个特点:
1.JTable的表头其实也是一个单独的组件,TableHeader
2.而每一列也可以被划分出来作为一个组件,TableColumn
3.JTable的数据选项不仅仅是可以显示字符串,还可以显示checkbox,img等等,其实只要你懂得如何去重写它内部的方法,你想怎么显示就怎么显示.
好了,废话不说了,来看
1.第一个例子(这个例子对官方的内容进行了少许变动,可以让别人更加理解这个属性的含义是什么)
1 package org.plx.jtable; 2 3 import java.awt.BorderLayout; 4 import java.awt.Color; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.awt.event.MouseAdapter; 8 import java.awt.event.MouseEvent; 9 10 import javax.swing.JButton; 11 import javax.swing.JFrame; 12 import javax.swing.JPanel; 13 import javax.swing.JScrollPane; 14 import javax.swing.JTable; 15 16 @SuppressWarnings("serial") 17 public class SimpleTableDemo extends JPanel { 18 private boolean DEBUG = true; 19 20 public SimpleTableDemo() { 21 super(new BorderLayout()); 22 23 //创建表头 24 String[] columnNames = { "First Name", "Last Name", "Sport", 25 "# of Years", "Vegetarian" }; 26 27 //创建显示数据 28 Object[][] data = { 29 { "Kathy", "Smith", "Snowboarding", new Integer(5), 30 new Boolean(false) }, 31 { "John", "Doe", "Rowing", new Integer(3), new Boolean(true) }, 32 { "Sue", "Black", "Knitting", new Integer(2), 33 new Boolean(false) }, 34 { "Jane", "White", "Speed reading", new Integer(20), 35 new Boolean(true) }, 36 { "Joe", "Brown", "Pool", new Integer(10), new Boolean(false) } }; 37 38 /* 39 * JTable还提供了一个重载的构造方法,传入两个Vector 40 * JTable(Vector rowData, Vector columnNames) 41 * 42 */ 43 44 final JTable table = new JTable(data, columnNames); 45 46 table.setBackground(Color.YELLOW); 47 48 //table.setPreferredScrollableViewportSize(new Dimension(500, 0)); 49 50 if (DEBUG) { 51 table.addMouseListener(new MouseAdapter() { 52 public void mouseClicked(MouseEvent e) { 53 printDebugData(table); 54 } 55 }); 56 } 57 58 // Create the scroll pane and add the table to it. 59 //这也是官方建议使用的方式,否则表头不会显示,需要单独获取到TableHeader自己手动地添加显示 60 JScrollPane scrollPane = new JScrollPane(table); 61 62 add(scrollPane); 63 64 65 JPanel panel2 = new JPanel(); 66 this.add(panel2,BorderLayout.SOUTH); 67 JButton btn1 = new JButton("表格填充整个视图"); 68 JButton btn2 = new JButton("表格不添加整个视图(默认不填充)"); 69 panel2.add(btn1); 70 panel2.add(btn2); 71 72 btn1.addActionListener(new ActionListener() { 73 @Override 74 public void actionPerformed(ActionEvent e) { 75 //设置表格填充整个视图,在默认情况下,如果表格的大小小于视图(窗体),你会发现下面的内容是其他颜色,可以将上面的yellow去掉做个比较 76 table.setFillsViewportHeight(true); 77 } 78 }); 79 80 btn2.addActionListener(new ActionListener() { 81 @Override 82 public void actionPerformed(ActionEvent e) { 83 table.setFillsViewportHeight(false); 84 } 85 }); 86 87 } 88 89 private void printDebugData(JTable table) { 90 int numRows = table.getRowCount(); 91 int numCols = table.getColumnCount(); 92 javax.swing.table.TableModel model = table.getModel(); 93 94 System.out.println("Value of data: "); 95 for (int i = 0; i < numRows; i++) { 96 System.out.print(" row " + i + ":"); 97 for (int j = 0; j < numCols; j++) { 98 System.out.print(" " + model.getValueAt(i, j)); 99 } 100 System.out.println(); 101 } 102 System.out.println("--------------------------"); 103 } 104 105 /** 106 * Create the GUI and show it. For thread safety, this method should be 107 * invoked from the event-dispatching thread. 108 */ 109 private static void createAndShowGUI() { 110 // Create and set up the window. 111 JFrame frame = new JFrame("SimpleTableDemo"); 112 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 113 114 // Create and set up the content pane. 115 SimpleTableDemo newContentPane = new SimpleTableDemo(); 116 newContentPane.setOpaque(true); // content panes must be opaque 117 frame.setContentPane(newContentPane); 118 119 // Display the window. 120 //frame.pack(); 121 frame.setSize(800, 600); 122 frame.setVisible(true); 123 } 124 125 public static void main(String[] args) { 126 // Schedule a job for the event-dispatching thread: 127 // creating and showing this application's GUI. 128 javax.swing.SwingUtilities.invokeLater(new Runnable() { 129 public void run() { 130 createAndShowGUI(); 131 } 132 }); 133 } 134 }
我在这里故意将表格的背景设置成YELLOW,可以让大家更加看清JTable在默认的情况下当它自身显示的大小小于容器时,它是不会去填充整个容器的,当设置setFillsViewportHeight
为true的时候,表格就会填充整个容器。官方对于设置这个的作用是可以表格更加容易作为被拖拽的目标,但是我至今还没试过往表格里托东西,事后准备试试。。。
在上面的代码中还有一点需要注意的是:表格的显示通常都会将其放置在JScrollpane,如果不放置,那么表格的表头就无法显示,而此时如果要显示的话,那么就得将表头(TableHeader,前面已经提到了)作为一个组件显示,而JTable再作为一个组件显示。
container.setLayout(new BorderLayout()); container.add(table.getTableHeader(),BorderLayout.PAGE_START); container.add(table, BorderLayout.CENTER);
分析一下上面的表格存在的缺陷:
(1).这个表格是可以被编辑的
(2).我们发现,传入的数据是Boolan类型,按照正常情况下,应该显示成checkbox,但是这里却是以字符串的形式显示(比较第一个图可以看到,它最后显示的是一个checkbox,可以让用户查看更加的直观,而第二种却只能死板的显示字符串),而为什么会造成这样的原因呢,后面我们分析完源码就彻底明白了。
(3).这里的创建需要我们自己先去创建数据或者Vector,但在很多情况下这种方式会对开发造成代码的冗余。
比如:我们从数据库中查询到一组对象,此时就需要将对象转成数组或者集合
(当然,如果你用AOP来解决这类问题可能大材小用了)
2.修改单元格的宽度:
我们可以试着去观察一下上面的表格,我们发现每列都是一样的宽度,但是在实际的开发中可能会遇到对象的某些属性是内容特别长的,但是某些属性内容却是特别端,因为可能会导致内容较长的列被遮挡,造成不好的用户体验.因此,设置表格列的宽度也是实际中必不可少的部分。OK,下面来完成一个对表格列宽度的设置。
1 package org.plx.jtable; 2 3 import java.awt.BorderLayout; 4 import java.awt.Color; 5 import java.awt.Dimension; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 import java.awt.event.MouseAdapter; 9 import java.awt.event.MouseEvent; 10 11 import javax.swing.JButton; 12 import javax.swing.JFrame; 13 import javax.swing.JPanel; 14 import javax.swing.JScrollPane; 15 import javax.swing.JTable; 16 import javax.swing.table.TableColumn; 17 18 @SuppressWarnings("serial") 19 public class SetColumnSizeDemo extends JPanel { 20 private boolean DEBUG = true; 21 22 public SetColumnSizeDemo() { 23 super(new BorderLayout()); 24 25 //创建表头 26 String[] columnNames = { "First Name", "Last Name", "Sport", 27 "# of Years", "Vegetarian" }; 28 29 //创建显示数据 30 Object[][] data = { 31 { "Kathy", "Smith", "Snowboarding", new Integer(5), 32 new Boolean(false) }, 33 { "John", "Doe", "Rowing", new Integer(3), new Boolean(true) }, 34 { "Sue", "Black", "Knitting", new Integer(2), 35 new Boolean(false) }, 36 { "Jane", "White", "Speed reading", new Integer(20), 37 new Boolean(true) }, 38 { "Joe", "Brown", "Pool", new Integer(10), new Boolean(false) } }; 39 40 /* 41 * JTable还提供了一个重载的构造方法,传入两个Vector 42 * JTable(Vector rowData, Vector columnNames) 43 * 44 */ 45 46 final JTable table = new JTable(data, columnNames); 47 48 table.setBackground(Color.YELLOW); 49 50 table.setPreferredScrollableViewportSize(new Dimension(800, 100)); 51 52 if (DEBUG) { 53 table.addMouseListener(new MouseAdapter() { 54 public void mouseClicked(MouseEvent e) { 55 printDebugData(table); 56 } 57 }); 58 } 59 60 // Create the scroll pane and add the table to it. 61 //这也是官方建议使用的方式,否则表头不会显示,需要单独获取到TableHeader自己手动地添加显示 62 JScrollPane scrollPane = new JScrollPane(table); 63 64 add(scrollPane); 65 66 67 JPanel panel2 = new JPanel(); 68 this.add(panel2,BorderLayout.SOUTH); 69 JButton btn1 = new JButton("表格填充整个视图"); 70 JButton btn2 = new JButton("表格不添加整个视图(默认不填充)"); 71 panel2.add(btn1); 72 panel2.add(btn2); 73 74 btn1.addActionListener(new ActionListener() { 75 @Override 76 public void actionPerformed(ActionEvent e) { 77 //设置表格填充整个视图,在默认情况下,如果表格的大小小于视图(窗体),你会发现下面的内容是其他颜色,可以将上面的yellow去掉做个比较 78 table.setFillsViewportHeight(true); 79 } 80 }); 81 82 btn2.addActionListener(new ActionListener() { 83 @Override 84 public void actionPerformed(ActionEvent e) { 85 table.setFillsViewportHeight(false); 86 } 87 }); 88 initColumnSize(table); 89 } 90 91 private void printDebugData(JTable table) { 92 int numRows = table.getRowCount(); 93 int numCols = table.getColumnCount(); 94 javax.swing.table.TableModel model = table.getModel(); 95 96 System.out.println("Value of data: "); 97 for (int i = 0; i < numRows; i++) { 98 System.out.print(" row " + i + ":"); 99 for (int j = 0; j < numCols; j++) { 100 System.out.print(" " + model.getValueAt(i, j)); 101 } 102 System.out.println(); 103 } 104 System.out.println("--------------------------"); 105 } 106 107 /** 108 * Create the GUI and show it. For thread safety, this method should be 109 * invoked from the event-dispatching thread. 110 */ 111 private static void createAndShowGUI() { 112 // Create and set up the window. 113 JFrame frame = new JFrame("SimpleTableDemo"); 114 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 115 116 // Create and set up the content pane. 117 SetColumnSizeDemo newContentPane = new SetColumnSizeDemo(); 118 newContentPane.setOpaque(true); // content panes must be opaque 119 frame.setContentPane(newContentPane); 120 121 // 这里直接使用pack来显示 122 frame.pack(); 123 frame.setVisible(true); 124 } 125 126 /** 127 * 设置Column的宽度 128 */ 129 private void initColumnSize(JTable table){ 130 //表格的每一列也是一个组件 131 TableColumn tc = null; 132 133 for(int i = 0 ;i < table.getColumnCount();i++){ 134 //注意:这里需要使用TableColumnModel来获取 135 //如果直接使用table.getColumn(identifier)会报错, 136 tc = table.getColumnModel().getColumn(i); 137 tc.setPreferredWidth(50 * (i+1)); 138 } 139 } 140 141 public static void main(String[] args) { 142 // Schedule a job for the event-dispatching thread: 143 // creating and showing this application's GUI. 144 javax.swing.SwingUtilities.invokeLater(new Runnable() { 145 public void run() { 146 createAndShowGUI(); 147 } 148 }); 149 } 150 }
可以显示一下效果,这边图片上传一直失败。还有一点要注意的是:表格的列默认情况下是可以拖动的,那么我们可以设置
tc.setMaxWidth(maxWidth);和 tc.setMinWidth(minWidth);来确定它拖动到最大和最小的宽度。最后补充一点:JTable可以设置setAutoResizeMode,可以传入一下的五个值:
AUTO_RESIZE_OFF,
AUTO_RESIZE_NEXT_COLUMN,
AUTO_RESIZE_SUBSEQUENT_COLUMNS,
AUTO_RESIZE_LAST_COLUMN,
AUTO_RESIZE_ALL_COLUMNS
/** * @see #getUIClassID * @see #readObject */ private static final String uiClassID = "TableUI"; /** Do not adjust column widths automatically; use a horizontal scrollbar instead. */ public static final int AUTO_RESIZE_OFF = 0; /** When a column is adjusted in the UI, adjust the next column the opposite way. */ public static final int AUTO_RESIZE_NEXT_COLUMN = 1; /** During UI adjustment, change subsequent columns to preserve the total width; * this is the default behavior. */ public static final int AUTO_RESIZE_SUBSEQUENT_COLUMNS = 2; /** During all resize operations, apply adjustments to the last column only. */ public static final int AUTO_RESIZE_LAST_COLUMN = 3; /** During all resize operations, proportionately resize all columns. */ public static final int AUTO_RESIZE_ALL_COLUMNS = 4;
默认情况下的值是2。上面代码中的注释已经说得很清楚了。
现在回到前面第一个案例中遗留下来的问题就是为什么有些单元格可以显示checkbox,有些只能显示字符串呢?
我们来看下面的这个例子:
package org.plx.jtable; import java.awt.Dimension; import java.awt.GridLayout; import javax.swing.DefaultCellEditor; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumn; @SuppressWarnings("serial") public class TableRenderDemo extends JPanel { private boolean DEBUG = false; public TableRenderDemo() { super(new GridLayout(1, 0)); JTable table = new JTable(new MyTableModel()); table.setPreferredScrollableViewportSize(new Dimension(500, 400)); table.setFillsViewportHeight(true); // Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(table); // Fiddle with the Sport column's cell editors/renderers. setUpSportColumn(table, table.getColumnModel().getColumn(2)); // Add the scroll pane to this panel. add(scrollPane); } @SuppressWarnings({ "rawtypes", "unchecked" }) public void setUpSportColumn(JTable table, TableColumn sportColumn) { // Set up the editor for the sport cells. JComboBox comboBox = new JComboBox(); comboBox.addItem("Snowboarding"); comboBox.addItem("Rowing"); comboBox.addItem("Knitting"); comboBox.addItem("Speed reading"); comboBox.addItem("Pool"); comboBox.addItem("None of the above"); sportColumn.setCellEditor(new DefaultCellEditor(comboBox)); // Set up tool tips for the sport cells. DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); renderer.setToolTipText("Click for combo box"); sportColumn.setCellRenderer(renderer); } class MyTableModel extends AbstractTableModel { private String[] columnNames = { "First Name", "Last Name", "Sport", "# of Years", "Vegetarian" }; private 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) } }; public final Object[] longValues = { "Jane", "Kathy", "None of the above", new Integer(20), Boolean.TRUE }; public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.length; } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { return data[row][col]; } /* * JTable uses this method to determine the default renderer/ editor for * each cell. If we didn't implement this method, then the last column * would contain text ("true"/"false"), rather than a check box. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } /* * Don't need to implement this method unless your table's editable. */ public boolean isCellEditable(int row, int col) { // Note that the data/cell address is constant, // no matter where the cell appears onscreen. if (col < 2) { return false; } else { return true; } } /* * Don't need to implement this method unless your table's data can * change. */ public void setValueAt(Object value, int row, int col) { if (DEBUG) { System.out.println("Setting value at " + row + "," + col + " to " + value + " (an instance of " + value.getClass() + ")"); } data[row][col] = value; fireTableCellUpdated(row, col); if (DEBUG) { System.out.println("New value of data:"); printDebugData(); } } private void printDebugData() { int numRows = getRowCount(); int numCols = getColumnCount(); for (int i = 0; i < numRows; i++) { System.out.print(" row " + i + ":"); for (int j = 0; j < numCols; j++) { System.out.print(" " + data[i][j]); } System.out.println(); } System.out.println("--------------------------"); } } /** * Create the GUI and show it. For thread safety, this method should be * invoked from the event-dispatching thread. */ private static void createAndShowGUI() { // Create and set up the window. JFrame frame = new JFrame("TableRenderDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane. TableRenderDemo newContentPane = new TableRenderDemo(); 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(); } }); } }
此时再次运行上面的程序就可以显示checkbox。首先我们这里自己去创建了一个TableModel的实现类
public interface TableModel { /** * Returns the number of rows in the model. A * <code>JTable</code> uses this method to determine how many rows it * should display. This method should be quick, as it * is called frequently during rendering. * * @return the number of rows in the model * @see #getColumnCount */ public int getRowCount();
TableModel是一个接口,而我们自己所编写的TableModel是继承了AbstractTableModel,该类是一个抽象类,实现了一部分的方法。但是最最核心和还是
/* * JTable uses this method to determine the default renderer/ editor for * each cell. If we didn't implement this method, then the last column * would contain text ("true"/"false"), rather than a check box. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); }
从以上的注释我们可以知道:JTable是使用这个方法来决定对于每个单元格是如何渲染的,如果我们不实现这个方法,那么默认返回的值就是true或者false,而不是一个checkbox。
其本质的原因在于如果当返回的是一个Object.class,那么JTable在渲染的时候就是使用字符串去显示,如果你返回的是Boolean.class,那么就是以checkbox来渲染,而如果是Image.class,那么就可以以图片的显示来显示。我们的第一个例子中,直接使用数组的方式来构建JTable,它的低层是通过自身的一个TableModel来维护,这个类是一个匿名内部类,
public JTable(final Object[][] rowData, final Object[] columnNames) { this(new AbstractTableModel() { public String getColumnName(int column) { return columnNames[column].toString(); } public int getRowCount() { return rowData.length; } public int getColumnCount() { return columnNames.length; } public Object getValueAt(int row, int col) { return rowData[row][col]; } public boolean isCellEditable(int row, int column) { return true; } public void setValueAt(Object value, int row, int col) { rowData[row][col] = value; fireTableCellUpdated(row, col); } }); }
通过以上的源码我们可以看到,它是继承了AbstractTableModel,而AbstractTableModel中的getColumnClass方法返回的就是一个Object.class,所以无论传入什么内容,JTable在显示的时候都是通过字符串去显示。
AbstractTableModel的getColumnClass方法:
package org.plx.jtable; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.ListSelectionModel; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.plaf.metal.MetalLookAndFeel; import javax.swing.table.AbstractTableModel; @SuppressWarnings("serial") public class TableSelectionDemo extends JPanel implements ActionListener { private JTable table; private JCheckBox rowCheck; private JCheckBox columnCheck; private JCheckBox cellCheck; private ButtonGroup buttonGroup; private JTextArea output; public TableSelectionDemo() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); table = new JTable(new MyTableModel()); table.setPreferredScrollableViewportSize(new Dimension(500, 70)); table.setFillsViewportHeight(true); table.getSelectionModel().addListSelectionListener(new RowListener()); table.getColumnModel().getSelectionModel() .addListSelectionListener(new ColumnListener()); add(new JScrollPane(table)); add(new JLabel("Selection Mode")); buttonGroup = new ButtonGroup(); //可以选中多行,使用ctrl或者shift都可以进行选择,默认情况下就是可以选择多行 addRadio("Multiple Interval Selection").setSelected(true); //只能选中单行 addRadio("Single Selection"); //只能选中连续的多行 addRadio("Single Interval Selection"); add(new JLabel("Selection Options")); //默认情况下就是以行为单位进行选择 rowCheck = addCheckBox("Row Selection"); rowCheck.setSelected(true); columnCheck = addCheckBox("Column Selection"); cellCheck = addCheckBox("Cell Selection"); cellCheck.setEnabled(false); output = new JTextArea(5, 40); output.setEditable(false); add(new JScrollPane(output)); } private JCheckBox addCheckBox(String text) { JCheckBox checkBox = new JCheckBox(text); checkBox.addActionListener(this); add(checkBox); return checkBox; } private JRadioButton addRadio(String text) { JRadioButton b = new JRadioButton(text); b.addActionListener(this); buttonGroup.add(b); add(b); return b; } public void actionPerformed(ActionEvent event) { String command = event.getActionCommand(); // Cell selection is disabled in Multiple Interval Selection // mode. The enabled state of cellCheck is a convenient flag // for this status. if ("Row Selection" == command) { table.setRowSelectionAllowed(rowCheck.isSelected()); // In MIS mode, column selection allowed must be the // opposite of row selection allowed. if (!cellCheck.isEnabled()) { table.setColumnSelectionAllowed(!rowCheck.isSelected()); } } else if ("Column Selection" == command) { table.setColumnSelectionAllowed(columnCheck.isSelected()); // In MIS mode, row selection allowed must be the // opposite of column selection allowed. if (!cellCheck.isEnabled()) { table.setRowSelectionAllowed(!columnCheck.isSelected()); } } else if ("Cell Selection" == command) { table.setCellSelectionEnabled(cellCheck.isSelected()); } else if ("Multiple Interval Selection" == command) { table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // If cell selection is on, turn it off. if (cellCheck.isSelected()) { cellCheck.setSelected(false); table.setCellSelectionEnabled(false); } // And don't let it be turned back on. cellCheck.setEnabled(false); } else if ("Single Interval Selection" == command) { table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); // Cell selection is ok in this mode. cellCheck.setEnabled(true); } else if ("Single Selection" == command) { table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // Cell selection is ok in this mode. cellCheck.setEnabled(true); } // Update checkboxes to reflect selection mode side effects. rowCheck.setSelected(table.getRowSelectionAllowed()); columnCheck.setSelected(table.getColumnSelectionAllowed()); if (cellCheck.isEnabled()) { cellCheck.setSelected(table.getCellSelectionEnabled()); } } private void outputSelection() { output.append(String.format("Lead: %d, %d. ", table.getSelectionModel() .getLeadSelectionIndex(), table.getColumnModel() .getSelectionModel().getLeadSelectionIndex())); output.append("Rows:"); for (int c : table.getSelectedRows()) { output.append(String.format(" %d", c)); } output.append(". Columns:"); for (int c : table.getSelectedColumns()) { output.append(String.format(" %d", c)); } output.append(". "); } private class RowListener implements ListSelectionListener { public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) { return; } output.append("ROW SELECTION EVENT. "); outputSelection(); } } private class ColumnListener implements ListSelectionListener { public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) { return; } output.append("COLUMN SELECTION EVENT. "); outputSelection(); } } class MyTableModel extends AbstractTableModel { private String[] columnNames = { "First Name", "Last Name", "Sport", "# of Years", "Vegetarian" }; private 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) } }; public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.length; } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { return data[row][col]; } @SuppressWarnings({ "unchecked", "rawtypes" }) public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } /* * Don't need to implement this method unless your table's editable. */ public boolean isCellEditable(int row, int col) { // Note that the data/cell address is constant, // no matter where the cell appears onscreen. if (col < 2) { return false; } else { return true; } } /* * Don't need to implement this method unless your table's data can * change. */ public void setValueAt(Object value, int row, int col) { data[row][col] = value; fireTableCellUpdated(row, col); } } /** * Create the GUI and show it. For thread safety, this method should be * invoked from the event-dispatching thread. */ private static void createAndShowGUI() { try { UIManager.setLookAndFeel(new MetalLookAndFeel()); JFrame frame = new JFrame("TableSelectionDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TableSelectionDemo newContentPane = new TableSelectionDemo(); newContentPane.setOpaque(true); // content panes must be opaque frame.setContentPane(newContentPane); // Display the window. frame.pack(); frame.setVisible(true); } catch (UnsupportedLookAndFeelException e) { throw new RuntimeException(e); } } 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(); } }); } }
以上代码的核心是理解API中的几个方法:
selectRowAllowed是否允许选中行,selectColumnAllowed是否选中列,当设置两个方法都为true的时候,那么此时就是设置你选中的是单元格.如果两个都设置false,那么就表示什么都无法选中。最后,如果你只能选中一个单元格,还需要设置selectListModel为singleSelection,对于singleSelection方法中的参数是通过获取到ListSelectionModel中的常量来完成设置。
package javax.swing; import javax.swing.event.*; /** * This interface represents the current state of the * selection for any of the components that display a * list of values with stable indices. The selection is * modeled as a set of intervals, each interval represents * a contiguous range of selected list elements. * The methods for modifying the set of selected intervals * all take a pair of indices, index0 and index1, that represent * a closed interval, i.e. the interval includes both index0 and * index1. * * @author Hans Muller * @author Philip Milne * @see DefaultListSelectionModel */ public interface ListSelectionModel { /** * A value for the selectionMode property: select one list index * at a time. * * @see #setSelectionMode */ int SINGLE_SELECTION = 0; /** * A value for the selectionMode property: select one contiguous * range of indices at a time. * * @see #setSelectionMode */ int SINGLE_INTERVAL_SELECTION = 1; /** * A value for the selectionMode property: select one or more * contiguous ranges of indices at a time. * * @see #setSelectionMode */ int MULTIPLE_INTERVAL_SELECTION = 2; /** * Changes the selection to be between {@code index0} and {@code index1} * inclusive. {@code index0} doesn't have to be less than or equal to * {@code index1}. * <p> * In {@code SINGLE_SELECTION} selection mode, only the second index * is used. * <p> * If this represents a change to the current selection, then each * {@code ListSelectionListener} is notified of the change. * * @param index0 one end of the interval. * @param index1 other end of the interval * @see #addListSelectionListener */ void setSelectionInterval(int index0, int index1); /** * Changes the selection to be the set union of the current selection * and the indices between {@code index0} and {@code index1} inclusive. * {@code index0} doesn't have to be less than or equal to {@code index1}. * <p> * In {@code SINGLE_SELECTION} selection mode, this is equivalent * to calling {@code setSelectionInterval}, and only the second index * is used. In {@code SINGLE_INTERVAL_SELECTION} selection mode, this * method behaves like {@code setSelectionInterval}, unless the given * interval is immediately adjacent to or overlaps the existing selection, * and can therefore be used to grow the selection. * <p> * If this represents a change to the current selection, then each * {@code ListSelectionListener} is notified of the change. * * @param index0 one end of the interval. * @param index1 other end of the interval * @see #addListSelectionListener * @see #setSelectionInterval */ void addSelectionInterval(int index0, int index1); /** * Changes the selection to be the set difference of the current selection * and the indices between {@code index0} and {@code index1} inclusive. * {@code index0} doesn't have to be less than or equal to {@code index1}. * <p> * In {@code SINGLE_INTERVAL_SELECTION} selection mode, if the removal * would produce two disjoint selections, the removal is extended through * the greater end of the selection. For example, if the selection is * {@code 0-10} and you supply indices {@code 5,6} (in any order) the * resulting selection is {@code 0-4}. * <p> * If this represents a change to the current selection, then each * {@code ListSelectionListener} is notified of the change. * * @param index0 one end of the interval. * @param index1 other end of the interval * @see #addListSelectionListener */ void removeSelectionInterval(int index0, int index1); /** * Returns the first selected index or -1 if the selection is empty. */ int getMinSelectionIndex(); /** * Returns the last selected index or -1 if the selection is empty. */ int getMaxSelectionIndex(); /** * Returns true if the specified index is selected. */ boolean isSelectedIndex(int index); /** * Return the first index argument from the most recent call to * setSelectionInterval(), addSelectionInterval() or removeSelectionInterval(). * The most recent index0 is considered the "anchor" and the most recent * index1 is considered the "lead". Some interfaces display these * indices specially, e.g. Windows95 displays the lead index with a * dotted yellow outline. * * @see #getLeadSelectionIndex * @see #setSelectionInterval * @see #addSelectionInterval */ int getAnchorSelectionIndex(); /** * Set the anchor selection index. * * @see #getAnchorSelectionIndex */ void setAnchorSelectionIndex(int index); /** * Return the second index argument from the most recent call to * setSelectionInterval(), addSelectionInterval() or removeSelectionInterval(). * * @see #getAnchorSelectionIndex * @see #setSelectionInterval * @see #addSelectionInterval */ int getLeadSelectionIndex(); /** * Set the lead selection index. * * @see #getLeadSelectionIndex */ void setLeadSelectionIndex(int index); /** * Change the selection to the empty set. If this represents * a change to the current selection then notify each ListSelectionListener. * * @see #addListSelectionListener */ void clearSelection(); /** * Returns true if no indices are selected. */ boolean isSelectionEmpty(); /** * Insert length indices beginning before/after index. This is typically * called to sync the selection model with a corresponding change * in the data model. */ void insertIndexInterval(int index, int length, boolean before); /** * Remove the indices in the interval index0,index1 (inclusive) from * the selection model. This is typically called to sync the selection * model width a corresponding change in the data model. */ void removeIndexInterval(int index0, int index1); /** * Sets the {@code valueIsAdjusting} property, which indicates whether * or not upcoming selection changes should be considered part of a single * change. The value of this property is used to initialize the * {@code valueIsAdjusting} property of the {@code ListSelectionEvent}s that * are generated. * <p> * For example, if the selection is being updated in response to a user * drag, this property can be set to {@code true} when the drag is initiated * and set to {@code false} when the drag is finished. During the drag, * listeners receive events with a {@code valueIsAdjusting} property * set to {@code true}. At the end of the drag, when the change is * finalized, listeners receive an event with the value set to {@code false}. * Listeners can use this pattern if they wish to update only when a change * has been finalized. * <p> * Setting this property to {@code true} begins a series of changes that * is to be considered part of a single change. When the property is changed * back to {@code false}, an event is sent out characterizing the entire * selection change (if there was one), with the event's * {@code valueIsAdjusting} property set to {@code false}. * * @param valueIsAdjusting the new value of the property * @see #getValueIsAdjusting * @see javax.swing.event.ListSelectionEvent#getValueIsAdjusting */ void setValueIsAdjusting(boolean valueIsAdjusting); /** * Returns {@code true} if the selection is undergoing a series of changes. * * @return true if the selection is undergoing a series of changes * @see #setValueIsAdjusting */ boolean getValueIsAdjusting(); /** * Sets the selection mode. The following list describes the accepted * selection modes: * <ul> * <li>{@code ListSelectionModel.SINGLE_SELECTION} - * Only one list index can be selected at a time. In this mode, * {@code setSelectionInterval} and {@code addSelectionInterval} are * equivalent, both replacing the current selection with the index * represented by the second argument (the "lead"). * <li>{@code ListSelectionModel.SINGLE_INTERVAL_SELECTION} - * Only one contiguous interval can be selected at a time. * In this mode, {@code addSelectionInterval} behaves like * {@code setSelectionInterval} (replacing the current selection), * unless the given interval is immediately adjacent to or overlaps * the existing selection, and can therefore be used to grow it. * <li>{@code ListSelectionModel.MULTIPLE_INTERVAL_SELECTION} - * In this mode, there's no restriction on what can be selected. * </ul> * * @see #getSelectionMode * @throws IllegalArgumentException if the selection mode isn't * one of those allowed */ void setSelectionMode(int selectionMode); /** * Returns the current selection mode. * * @return the current selection mode * @see #setSelectionMode */ int getSelectionMode(); /** * Add a listener to the list that's notified each time a change * to the selection occurs. * * @param x the ListSelectionListener * @see #removeListSelectionListener * @see #setSelectionInterval * @see #addSelectionInterval * @see #removeSelectionInterval * @see #clearSelection * @see #insertIndexInterval * @see #removeIndexInterval */ void addListSelectionListener(ListSelectionListener x); /** * Remove a listener from the list that's notified each time a * change to the selection occurs. * * @param x the ListSelectionListener * @see #addListSelectionListener */ void removeListSelectionListener(ListSelectionListener x); }
TableModel:
在前面我们已经提到过TableModel是用来管理真实的数据.我们在实际的开发中通常会使用DefaultTableModel。在创建JTable的时候,如果你传入的是一个Object[],那么底层维护的TableModel是一个AbstractModel的匿名内部类,这才前面已经提到,但是如果你传入的是Vector,那么底层维护的是DefaultTableModel.
底层源码:
public JTable(Vector rowData, Vector columnNames) { this(new DefaultTableModel(rowData, columnNames)); }
但是我们最希望的是直接丢一个对象就去,然后显示。下面我们就自己编写一个TableModel来完成一下的操作(当然,如果你想要你的程序更加的灵活,那么在TableModel中使用反射和内省机制吧)。对于自己实现TableModel,我们通常会去继承AbstractTableModel.
下面我们自己编写一个基于面向对象的TableModel,然后你再拿和传统的方式去比较,让你马上感觉从Jdbc上升到Hibernate的级别。当然,要编写一个通用的组件可能还需要测试,这里我这是提供一种思路来给大家参考而已。
package org.plx.jtable; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; public class User implements PropertyChangeListener{ @BeanColumn(name = "用户编号", index = 0) private Integer id; @BeanColumn(name = "用户姓名", index = 1) private String username; @BeanColumn(name = "用户密码", index = 2) private String password; @BeanColumn(name = "用户年龄", index = 3) private int age; //这里如果取名字为is开头,在introspector中可能会存在BUG @BeanColumn(name = "婚否", index = 4) private Boolean marry; public User() {} public User(Integer id, String username, String password, int age, Boolean marry) { this.id = id; this.username = username; this.password = password; this.age = age; this.marry = marry; } public Boolean getMarry() { return marry; } public void setMarry(Boolean marry) { this.marry = marry; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public void propertyChange(PropertyChangeEvent evt) { } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } }
在这里,我自己定义了一个Annotation,用来定义这个属性在JTable中对应的表头名字。
package org.plx.jtable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface BeanColumn { String name();//在JTable中的表头名字 int index();//在表头的索引 }
好了,下面我们来测试一下写的程序。
1 package org.plx.jtable; 2 3 import java.awt.BorderLayout; 4 import java.awt.FlowLayout; 5 import java.awt.Panel; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 import java.beans.BeanInfo; 9 import java.beans.IntrospectionException; 10 import java.beans.Introspector; 11 import java.beans.PropertyDescriptor; 12 import java.lang.reflect.Field; 13 import java.util.ArrayList; 14 import java.util.Enumeration; 15 import java.util.HashMap; 16 import java.util.List; 17 import java.util.Map; 18 import java.util.TreeMap; 19 20 import javax.swing.AbstractButton; 21 import javax.swing.ButtonGroup; 22 import javax.swing.JButton; 23 import javax.swing.JDialog; 24 import javax.swing.JFrame; 25 import javax.swing.JLabel; 26 import javax.swing.JRadioButton; 27 import javax.swing.JScrollPane; 28 import javax.swing.JTable; 29 import javax.swing.JTextField; 30 import javax.swing.table.AbstractTableModel; 31 import javax.swing.table.TableModel; 32 33 /** 34 * 基于对象格式的TableModel 35 * 36 * @author Administrator 37 * 38 */ 39 public class TableModelDemo { 40 41 public static void main(String[] args) { 42 43 final int WIDTH = 700; 44 final int HEIGHT = 500; 45 final int USER_SIZE = 10; 46 47 final JFrame frame = new JFrame(); 48 frame.setTitle("基于面向对象的TableModel测试"); 49 frame.setSize(WIDTH, HEIGHT); 50 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 51 52 final JTable table = new JTable(); 53 table.setFillsViewportHeight(true); 54 table.setRowHeight(30); 55 List<User> users = new ArrayList<User>(); 56 for (int i = 0; i < USER_SIZE; i++) { 57 User u = new User(); 58 u.setId(i); 59 u.setUsername("username" + i); 60 u.setPassword("123"); 61 u.setAge(i + 20); 62 u.setMarry(i % 2 == 0); 63 users.add(u); 64 } 65 66 final TableModel tableModel = new MyTableModel<User>(users); 67 table.setModel(tableModel); 68 69 frame.add(new JScrollPane(table)); 70 71 Panel panel = new Panel(); 72 73 JButton addBtn = new JButton("添加用户"); 74 JButton deleteBtn = new JButton("删除用户(按照行来删除)"); 75 JButton deleteBtn2 = new JButton("删除用户(按照对象来删除)"); 76 JButton updateBtn = new JButton("更新用户"); 77 78 panel.add(addBtn); 79 panel.add(deleteBtn); 80 panel.add(deleteBtn2); 81 panel.add(updateBtn); 82 83 addBtn.addActionListener(new ActionListener() { 84 @Override 85 public void actionPerformed(ActionEvent e) { 86 JDialog dialog = new JDialog(frame, true); 87 dialog.setSize(300, 300); 88 89 JLabel idLab = new JLabel("用户编号:"); 90 final JTextField idTextField = new JTextField(25); 91 92 JLabel nameLab = new JLabel("用户姓名"); 93 final JTextField nameTextField = new JTextField(25); 94 95 JLabel passwordLab = new JLabel("用户密码:"); 96 final JTextField passwordTextField = new JTextField(25); 97 98 JLabel ageLab = new JLabel("用户年龄:"); 99 final JTextField ageTextField = new JTextField(25); 100 101 final JRadioButton marry = new JRadioButton(); 102 marry.setText("已婚"); 103 marry.setSelected(true); 104 105 final JRadioButton noMarry = new JRadioButton(); 106 noMarry.setText("未婚"); 107 108 final ButtonGroup group = new ButtonGroup(); 109 group.add(marry); 110 group.add(noMarry); 111 112 JButton button = new JButton("确认添加"); 113 114 button.addActionListener(new ActionListener() { 115 @SuppressWarnings({ "unchecked", "rawtypes" }) 116 @Override 117 public void actionPerformed(ActionEvent e) { 118 Integer id = Integer.parseInt(idTextField.getText()); 119 String username = nameTextField.getText(); 120 String password = passwordTextField.getText(); 121 int age = Integer.parseInt(ageTextField.getText()); 122 boolean marry = false; 123 Enumeration<AbstractButton> en = group.getElements(); 124 for (; en.hasMoreElements();) { 125 AbstractButton ab = en.nextElement(); 126 if (ab.isSelected()) { 127 marry = ab.getText().equals("已婚") ? true 128 : false; 129 break; 130 } 131 } 132 User user = new User(id, username, password, age, marry); 133 ((MyTableModel) tableModel).addRow(user); 134 } 135 }); 136 137 dialog.setLayout(new FlowLayout()); 138 dialog.add(idLab); 139 dialog.add(idTextField); 140 dialog.add(nameLab); 141 dialog.add(nameTextField); 142 dialog.add(passwordLab); 143 dialog.add(passwordTextField); 144 dialog.add(ageLab); 145 dialog.add(ageTextField); 146 dialog.add(marry); 147 dialog.add(noMarry); 148 149 dialog.add(button); 150 151 dialog.setVisible(true); 152 } 153 }); 154 155 deleteBtn.addActionListener(new ActionListener() { 156 @SuppressWarnings("rawtypes") 157 @Override 158 public void actionPerformed(ActionEvent e) { 159 int rowIndex = table.getSelectedRow(); 160 ((MyTableModel)table.getModel()).deleteRow(rowIndex); 161 } 162 }); 163 164 165 deleteBtn2.addActionListener(new ActionListener() { 166 @SuppressWarnings({ "rawtypes", "unchecked" }) 167 @Override 168 public void actionPerformed(ActionEvent e) { 169 int rowIndex = table.getSelectedRow(); 170 171 MyTableModel tableModel = ((MyTableModel)table.getModel()); 172 Integer id = (Integer) tableModel.getValueAt(rowIndex, 0); 173 User u = new User(); 174 u.setId(id); 175 tableModel.deleteRow(u); 176 } 177 }); 178 179 updateBtn.addActionListener(new ActionListener() { 180 181 @Override 182 public void actionPerformed(ActionEvent e) { 183 final int rowIndex = table.getSelectedRow(); 184 185 @SuppressWarnings("rawtypes") 186 final MyTableModel tableModel = ((MyTableModel)table.getModel()); 187 /* 188 //传统的方式是需要通过 189 for(int i = 0;i < tableModel.getColumnCount();i++){ 190 tableModel.getValueAt(rowIndex, i); 191 } 192 */ 193 //现在我们采用基于OO的获取 194 User user = (User) tableModel.getObjbyRowIndex(rowIndex); 195 196 JDialog dialog = new JDialog(frame, true); 197 dialog.setSize(300, 300); 198 199 JLabel idLab = new JLabel("用户编号:"); 200 final JTextField idTextField = new JTextField(String.valueOf(user.getId()),25); 201 202 203 JLabel nameLab = new JLabel("用户姓名"); 204 final JTextField nameTextField = new JTextField(user.getUsername(),25); 205 206 JLabel passwordLab = new JLabel("用户密码:"); 207 final JTextField passwordTextField = new JTextField(user.getPassword(),25); 208 209 JLabel ageLab = new JLabel("用户年龄:"); 210 final JTextField ageTextField = new JTextField(String.valueOf(user.getAge()),25); 211 212 final JRadioButton marry = new JRadioButton(); 213 marry.setText("已婚"); 214 215 final JRadioButton noMarry = new JRadioButton(); 216 noMarry.setText("未婚"); 217 218 if(user.getMarry()){ 219 marry.setSelected(true); 220 } 221 else{ 222 noMarry.setSelected(true); 223 } 224 225 final ButtonGroup group = new ButtonGroup(); 226 group.add(marry); 227 group.add(noMarry); 228 229 JButton button = new JButton("确认更新"); 230 231 button.addActionListener(new ActionListener() { 232 @SuppressWarnings({ "unchecked"}) 233 @Override 234 public void actionPerformed(ActionEvent e) { 235 Integer id = Integer.parseInt(idTextField.getText()); 236 String username = nameTextField.getText(); 237 String password = passwordTextField.getText(); 238 int age = Integer.parseInt(ageTextField.getText()); 239 boolean marry = false; 240 Enumeration<AbstractButton> en = group.getElements(); 241 for (; en.hasMoreElements();) { 242 AbstractButton ab = en.nextElement(); 243 if (ab.isSelected()) { 244 marry = ab.getText().equals("已婚") ? true 245 : false; 246 break; 247 } 248 } 249 User user = new User(id, username, password, age, marry); 250 tableModel.update(rowIndex, user); 251 } 252 }); 253 254 dialog.setLayout(new FlowLayout()); 255 dialog.add(idLab); 256 dialog.add(idTextField); 257 dialog.add(nameLab); 258 dialog.add(nameTextField); 259 dialog.add(passwordLab); 260 dialog.add(passwordTextField); 261 dialog.add(ageLab); 262 dialog.add(ageTextField); 263 dialog.add(marry); 264 dialog.add(noMarry); 265 266 dialog.add(button); 267 268 dialog.setVisible(true); 269 } 270 }); 271 272 frame.add(panel, BorderLayout.SOUTH); 273 274 frame.setVisible(true); 275 } 276 277 } 278 279 /** 280 * 请将你传入的对象以JavaBean的形式创建 281 * 282 * 自己定义的TableModel,可以直接放入对象 283 * 284 * @author Administrator 285 * 286 * @param <T> 287 */ 288 @SuppressWarnings("serial") 289 class MyTableModel<T> extends AbstractTableModel { 290 private List<T> objs; 291 private BeanInfo beanInfo; 292 private Map<Integer, String> columnInfo = null; 293 private Map<Integer, Integer> propertyInfo = null; 294 private PropertyDescriptor[] pd = null; 295 private int columnCount; 296 @SuppressWarnings("unused") 297 private Class<T> clazz; 298 299 public MyTableModel() { 300 try { 301 columnInfo = new TreeMap<Integer, String>(); 302 propertyInfo = new HashMap<Integer, Integer>(); 303 Field[] fields = getClz().getDeclaredFields(); 304 beanInfo = Introspector.getBeanInfo(getClz()); 305 pd = beanInfo.getPropertyDescriptors(); 306 for (Field f : fields) { 307 if (f.isAnnotationPresent(BeanColumn.class)) { 308 // 这里没有直接写成columnCOunt = fileds.length是因为可能某些字段不用来显示 309 columnCount++; 310 // 获取到Annotation 311 BeanColumn bc = f.getAnnotation(BeanColumn.class); 312 // 获取到该属性对应的列名称 313 String columnName = bc.name(); 314 // 获取该名称在Table中的索引值 315 int index = bc.index(); 316 // 通过TreeMap将列名称以及它的索引存储起来,用来显示表头信息 317 columnInfo.put(index, columnName); 318 /* 319 * 判断该属性在beanInfo中的索引 320 * 最后显示是通过columnIndex--PropertyDescriptor数组中的索引, 321 * 然后获取到PropertyDescriptor来获取到具体的数据 322 */ 323 for (int i = 0; i < pd.length; i++) { 324 String fieldName = null; 325 if (f.getName().startsWith("is")) { 326 fieldName = f 327 .getName() 328 .substring( 329 f.getName().indexOf("is") 330 + "is".length()) 331 .toLowerCase(); 332 } else { 333 fieldName = f.getName(); 334 } 335 if (fieldName.equals(pd[i].getName())) { 336 propertyInfo.put(index, i); 337 } 338 } 339 } 340 } 341 } catch (IntrospectionException e) { 342 e.printStackTrace(); 343 } 344 } 345 346 public MyTableModel(List<T> list) { 347 this(); 348 this.objs = list; 349 } 350 351 352 /** 353 * 获取到泛型中的Class对象 354 * 这里还未解决,暂时先写死 355 * @return 356 */ 357 @SuppressWarnings("unchecked") 358 private Class<T> getClz() { 359 return (Class<T>) User.class; 360 } 361 362 /** 363 * 返回对象集合 364 * 365 * @return 366 */ 367 public List<T> getObjs() { 368 return objs; 369 } 370 371 /** 372 * 设置对象集合 373 * 374 * @param objs 375 */ 376 public void setObjs(List<T> objs) { 377 this.objs = objs; 378 } 379 380 /** 381 * 获取总的行数 382 */ 383 @Override 384 public int getRowCount() { 385 if (objs != null) { 386 return objs.size(); 387 } else { 388 return 0; 389 } 390 } 391 392 /** 393 * 获取总的列数 394 */ 395 @Override 396 public int getColumnCount() { 397 return columnCount; 398 } 399 400 /** 401 * 返回单元格的数据做显示 402 */ 403 @Override 404 public Object getValueAt(int rowIndex, int columnIndex) { 405 try { 406 if (objs != null) { 407 // 获取到行数据 408 T t = objs.get(rowIndex); 409 Integer propertyIndex = propertyInfo.get(columnIndex); 410 return pd[propertyIndex].getReadMethod().invoke(t, 411 new Object[] {}); 412 } 413 } catch (Exception e) { 414 e.printStackTrace(); 415 } 416 return null; 417 } 418 419 /** 420 * 返回类的名称 421 */ 422 @Override 423 public String getColumnName(int column) { 424 return columnInfo.get(column); 425 } 426 427 /** 428 * 返回TableCellRender渲染的类型 429 */ 430 @Override 431 public Class<?> getColumnClass(int columnIndex) { 432 if (pd != null) { 433 return pd[propertyInfo.get(columnIndex)].getPropertyType(); 434 } 435 return Object.class; 436 } 437 438 /** 439 * DefaultTableModel底层也是这样去完成的 440 */ 441 @Override 442 public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 443 try { 444 T t = objs.get(rowIndex); 445 int propIndex = propertyInfo.get(columnIndex); 446 pd[propIndex].getWriteMethod().invoke(t, new Object[] { aValue }); 447 // 当数据更新完成之后完成更新视图层 448 fireTableCellUpdated(rowIndex, columnIndex); 449 } catch (Exception e) { 450 e.printStackTrace(); 451 } 452 } 453 454 /** 455 * 设置是否可以编辑 456 */ 457 @Override 458 public boolean isCellEditable(int rowIndex, int columnIndex) { 459 return true; 460 } 461 462 public void addRow(T t) { 463 if (t == null) { 464 throw new RuntimeException("添加失败"); 465 } 466 objs.add(t); 467 fireTableRowsInserted(getRowCount() - 1, getRowCount() - 1); 468 } 469 470 /** 471 * 提供重载方法,让用户去调用 472 * 473 * @param data 474 */ 475 public void addRow(List<Object> data) { 476 477 } 478 479 public void addRow(Object[] data) { 480 481 } 482 483 /** 484 * 根据对象来删除 485 * 此时需要重写对象的equals和hashCode方法,因为底层ArrayLiST判断对象是否 486 * 相等是通过equals方法来进行比较 487 * @param t 488 */ 489 public void deleteRow(T t){ 490 this.objs.remove(t); 491 fireTableRowsDeleted(this.getColumnCount(),this.getColumnCount()); 492 } 493 494 /** 495 * 根据行来删除 496 * @param rowIndex 497 */ 498 public void deleteRow(int rowIndex){ 499 this.objs.remove(rowIndex); 500 fireTableRowsDeleted(this.getColumnCount(),this.getColumnCount()); 501 } 502 503 public T getObjbyRowIndex(int rowIndex){ 504 return objs.get(rowIndex); 505 } 506 507 /** 508 * 更新行数据 509 * @param rowIndex 510 * @param t 511 */ 512 public void update(int rowIndex,T t){ 513 this.objs.set(rowIndex, t); 514 fireTableRowsUpdated(this.getColumnCount() - 1, this.getRowCount() - 1); 515 } 516 517 }
如果我们自己编写TableModel的时候,在更新数据之后还得调用方法让去通知视图层去重新显示,我们在继承了AbstractTableModel之后就使用了以下的方法。
Method | Change |
---|---|
fireTableCellUpdated |
Update of specified cell. |
fireTableRowsUpdated |
Update of specified rows |
fireTableDataChanged |
Update of entire table (data only). |
fireTableRowsInserted |
New rows inserted. |
fireTableRowsDeleted |
Existing rows Deleted |
fireTableStructureChanged |
Invalidate entire table, both data and structure. |
下面介绍两个概念:一个叫渲染,一个叫编辑。
我们先介绍渲染:我们需要知道的是其实每一个单元格都是绘制出来的,而每一个单元格其实也是一个组件(Component)。Swing为了性能的原因,对于每一列使用一种单元格渲染器来渲染所有的单元格。这是我们要介绍的重点。
JTable默认的渲染器是DefaultTableCellRenderer,通过查看源码我们会发现原来这是一个JLabel啊!
/** * @(#)DefaultTableCellRenderer.java 1.48 08/09/18 * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.swing.table; import javax.swing.*; import javax.swing.table.TableCellRenderer; import javax.swing.border.*; import java.awt.Component; import java.awt.Color; import java.awt.Rectangle; import java.io.Serializable; import sun.swing.DefaultLookup; /*** * The standard class for rendering (displaying) individual cells * in a <code>JTable</code>. * <p> * * <strong><a name="override">Implementation Note:</a></strong> * This class inherits from <code>JLabel</code>, a standard component class. * However <code>JTable</code> employs a unique mechanism for rendering * its cells and therefore requires some slightly modified behavior * from its cell renderer. * The table class defines a single cell renderer and uses it as a * as a rubber-stamp for rendering all cells in the table; * it renders the first cell, * changes the contents of that cell renderer, * shifts the origin to the new location, re-draws it, and so on. * The standard <code>JLabel</code> component was not * designed to be used this way and we want to avoid * triggering a <code>revalidate</code> each time the * cell is drawn. This would greatly decrease performance because the * <code>revalidate</code> message would be * passed up the hierarchy of the container to determine whether any other * components would be affected. * As the renderer is only parented for the lifetime of a painting operation * we similarly want to avoid the overhead associated with walking the * hierarchy for painting operations. * So this class * overrides the <code>validate</code>, <code>invalidate</code>, * <code>revalidate</code>, <code>repaint</code>, and * <code>firePropertyChange</code> methods to be * no-ops and override the <code>isOpaque</code> method solely to improve * performance. If you write your own renderer, * please keep this performance consideration in mind. * <p> * * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans<sup><font size="-2">TM</font></sup> * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @version 1.48 09/18/08 * @author Philip Milne * @see JTable */ public class DefaultTableCellRenderer extends JLabel implements TableCellRenderer, Serializable { /*** * An empty <code>Border</code>. This field might not be used. To change the * <code>Border</code> used by this renderer override the * <code>getTableCellRendererComponent</code> method and set the border * of the returned component directly. */ private static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER; // We need a place to store the color the JLabel should be returned // to after its foreground and background colors have been set // to the selection background color. // These ivars will be made protected when their names are finalized. private Color unselectedForeground; private Color unselectedBackground; /*** * Creates a default table cell renderer. */ public DefaultTableCellRenderer() { super(); setOpaque(true); setBorder(getNoFocusBorder()); setName("Table.cellRenderer"); } private Border getNoFocusBorder() { Border border = DefaultLookup.getBorder(this, ui, "Table.cellNoFocusBorder"); if (System.getSecurityManager() != null) { if (border != null) return border; return SAFE_NO_FOCUS_BORDER; } else if (border != null) { if (noFocusBorder == null || noFocusBorder == DEFAULT_NO_FOCUS_BORDER) { return border; } } return noFocusBorder; } /*** * Overrides <code>JComponent.setForeground</code> to assign * the unselected-foreground color to the specified color. * * @param c set the foreground color to this value */ public void setForeground(Color c) { super.setForeground(c); unselectedForeground = c; } /*** * Overrides <code>JComponent.setBackground</code> to assign * the unselected-background color to the specified color. * * @param c set the background color to this value */ public void setBackground(Color c) { super.setBackground(c); unselectedBackground = c; } /*** * Notification from the <code>UIManager</code> that the look and feel * [L&F] has changed. * Replaces the current UI object with the latest version from the * <code>UIManager</code>. * * @see JComponent#updateUI */ public void updateUI() { super.updateUI(); setForeground(null); setBackground(null); } // implements javax.swing.table.TableCellRenderer /*** * * Returns the default table cell renderer. * <p> * During a printing operation, this method will be called with * <code>isSelected</code> and <code>hasFocus</code> values of * <code>false</code> to prevent selection and focus from appearing * in the printed output. To do other customization based on whether * or not the table is being printed, check the return value from * {@link javax.swing.JComponent#isPaintingForPrint()}. * * @param table the <code>JTable</code> * @param value the value to assign to the cell at * <code>[row, column]</code> * @param isSelected true if cell is selected * @param hasFocus true if cell has focus * @param row the row of the cell to render * @param column the column of the cell to render * @return the default table cell renderer * @see javax.swing.JComponent#isPaintingForPrint() */ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Color fg = null; Color bg = null; JTable.DropLocation dropLocation = table.getDropLocation(); if (dropLocation != null && !dropLocation.isInsertRow() && !dropLocation.isInsertColumn() && dropLocation.getRow() == row && dropLocation.getColumn() == column) { fg = DefaultLookup.getColor(this, ui, "Table.dropCellForeground"); bg = DefaultLookup.getColor(this, ui, "Table.dropCellBackground"); isSelected = true; } if (isSelected) { super.setForeground(fg == null ? table.getSelectionForeground() : fg); super.setBackground(bg == null ? table.getSelectionBackground() : bg); } else { Color background = unselectedBackground != null ? unselectedBackground : table.getBackground(); if (background == null || background instanceof javax.swing.plaf.UIResource) { Color alternateColor = DefaultLookup.getColor(this, ui, "Table.alternateRowColor"); if (alternateColor != null && row % 2 == 0) background = alternateColor; } super.setForeground(unselectedForeground != null ? unselectedForeground : table.getForeground()); super.setBackground(background); } setFont(table.getFont()); if (hasFocus) { Border border = null; if (isSelected) { border = DefaultLookup.getBorder(this, ui, "Table.focusSelectedCellHighlightBorder"); } if (border == null) { border = DefaultLookup.getBorder(this, ui, "Table.focusCellHighlightBorder"); } setBorder(border); if (!isSelected && table.isCellEditable(row, column)) { Color col; col = DefaultLookup.getColor(this, ui, "Table.focusCellForeground"); if (col != null) { super.setForeground(col); } col = DefaultLookup.getColor(this, ui, "Table.focusCellBackground"); if (col != null) { super.setBackground(col); } } } else { setBorder(getNoFocusBorder()); } setValue(value); return this; } /** * The following methods are overridden as a performance measure to * to prune code-paths are often called in the case of renders * but which we know are unnecessary. Great care should be taken * when writing your own renderer to weigh the benefits and * drawbacks of overriding methods like these. */ /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ public boolean isOpaque() { Color back = getBackground(); Component p = getParent(); if (p != null) { p = p.getParent(); } // p should now be the JTable. boolean colorMatch = (back != null) && (p != null) && back.equals(p.getBackground()) && p.isOpaque(); return !colorMatch && super.isOpaque(); } /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. * * @since 1.5 */ public void invalidate() {} /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ public void validate() {} /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ public void revalidate() {} /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ public void repaint(long tm, int x, int y, int width, int height) {} /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ public void repaint(Rectangle r) { } /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. * * @since 1.5 */ public void repaint() { } /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { // Strings get interned... if (propertyName=="text" || propertyName == "labelFor" || propertyName == "displayedMnemonic" || ((propertyName == "font" || propertyName == "foreground") && oldValue != newValue && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { super.firePropertyChange(propertyName, oldValue, newValue); } } /*** * Overridden for performance reasons. * See the <a href="#override">Implementation Note</a> * for more information. */ public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { } /*** * Sets the <code>String</code> object for the cell being rendered to * <code>value</code>. * * @param value the string value for this cell; if value is * <code>null</code> it sets the text value to an empty string * @see JLabel#setText * */ protected void setValue(Object value) { setText((value == null) ? "" : value.toString()); } /*** * A subclass of <code>DefaultTableCellRenderer</code> that * implements <code>UIResource</code>. * <code>DefaultTableCellRenderer</code> doesn't implement * <code>UIResource</code> * directly so that applications can safely override the * <code>cellRenderer</code> property with * <code>DefaultTableCellRenderer</code> subclasses. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans<sup><font size="-2">TM</font></sup> * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. */ public static class UIResource extends DefaultTableCellRenderer implements javax.swing.plaf.UIResource { } }
在JTable中有这样一段代码:
/** * Creates default cell renderers for objects, numbers, doubles, dates, * booleans, and icons. * @see javax.swing.table.DefaultTableCellRenderer * */ protected void createDefaultRenderers() { defaultRenderersByColumnClass = new UIDefaults(8, 0.75f); // Objects setLazyRenderer(Object.class, "javax.swing.table.DefaultTableCellRenderer$UIResource"); // Numbers setLazyRenderer(Number.class, "javax.swing.JTable$NumberRenderer"); // Doubles and Floats setLazyRenderer(Float.class, "javax.swing.JTable$DoubleRenderer"); setLazyRenderer(Double.class, "javax.swing.JTable$DoubleRenderer"); // Dates setLazyRenderer(Date.class, "javax.swing.JTable$DateRenderer"); // Icons and ImageIcons setLazyRenderer(Icon.class, "javax.swing.JTable$IconRenderer"); setLazyRenderer(ImageIcon.class, "javax.swing.JTable$IconRenderer"); // Booleans setLazyRenderer(Boolean.class, "javax.swing.JTable$BooleanRenderer"); }
JTableton在HashTable中存放Class对象作为KEY,渲染器的全称作为Value,当调用TableModel中的getColumnClass的时候,然后就会去判断到底对于这列的数据使用什么渲染方式,其实TableColumn也可以是设置CellRender。我们就来看看javax.swing.JTable$BooleanRender是怎么实现的。
1 static class BooleanRenderer extends JCheckBox implements TableCellRenderer, UIResource 2 { 3 private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); 4 5 public BooleanRenderer() { 6 super(); 7 setHorizontalAlignment(JLabel.CENTER); 8 setBorderPainted(true); 9 } 10 11 public Component getTableCellRendererComponent(JTable table, Object value, 12 boolean isSelected, boolean hasFocus, int row, int column) { 13 if (isSelected) { 14 setForeground(table.getSelectionForeground()); 15 super.setBackground(table.getSelectionBackground()); 16 } 17 else { 18 setForeground(table.getForeground()); 19 setBackground(table.getBackground()); 20 } 21 setSelected((value != null && ((Boolean)value).booleanValue())); 22 23 if (hasFocus) { 24 setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); 25 } else { 26 setBorder(noFocusBorder); 27 } 28 29 return this; 30 } 31 }
正所谓源码面前,了无秘密。现在应该知道是怎么回事了吧。这里需要注意的是,我们返回的Class类型是包装类,而不是原生数据类型。
好了,下面我们自己编写两个渲染器给大家瞧瞧。
table.setDefaultRenderer(Object.class, new DefaultTableCellHeaderRenderer() { @Override public Component getTableCellRendererComponent( JTable arg0, Object arg1, boolean arg2, boolean arg3, int row, int arg5) { super.getTableCellRendererComponent( arg0, arg1, arg2, arg3, row, arg5); if(row % 2 == 0){ this.setBackground(Color.BLUE); } return this; } });
我们将TabeModel中的代码所谓做修改
/** * 返回TableCellRender渲染的类型 */ @Override public Class<?> getColumnClass(int columnIndex) { // if (pd != null) { // return pd[propertyInfo.get(columnIndex)].getPropertyType(); // } return Object.class; }
怎么样,SO Easy吧!
如果我们自己想要编写特定的渲染器,我们可以实现TableCellRender接口来完成。这部分内容将在下篇中介绍、、、、、、、、