好久沒有玩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(".\n"); } 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接口來完成。這部分內容將在下篇中介紹、、、、、、、、