注:本文為學習筆記,原文為How to Use Tables,本文所有素材與代碼均源於原文,可能會有部分更改。
JTable是Swing中的表格控件,它的外觀如下所示:
沒錯,excel或者access數據庫的編輯區就是JTable這樣的控件了。
創建JTable
JTable提供了2個構造器可以讓你用數據和頭部直接生成它:
JTable(Object[][] rowData, Object[] columnNames)
JTable(Vector rowData, Vector columnNames)
這兩個構造器有一些特性你必須要注意:
1.JTable所有的單元格都是可編輯的;
2.它將所有數據都當做string來處理。本來,JTable可以將布爾型數據用一個checkBox來進行展示,就像表1那樣,在這里就不行了。
3.它要求你把所有數據都放到數組或vector中。如果你的數據來自於數據庫,那么專門再填充到數組實在是多此一舉。
吶,如果你不能忍受以上限制,那么就使用table Model來管理你的數據吧!
創建TableModel
如果程序沒有顯式地指定tableModel,JTable會自動生成一個 DefaultTableModel實例,這樣做的副作用在上面已經說過了。我們自己創建tableModel可以讓數據得到更好的展示,這樣做的方法是繼承
AbstractTableModel。它已經提供了tableModel接口的大部分默認實現,在最低限度下,你只需要實現以下三個方法:
public int getRowCount();
public int getColumnCount();
public Object getValueAt(int row, int column);
當然,如果你的應用程序有其他定制化的功能,你可以自己實現AbstractTableModel的其它方法,比如:
public Class getColumnClass(int c)//JTable uses this method to determine the default renderer editor for each cell
public boolean isCellEditable(int row, int col)//Don't need to implement this method unless your table's editable
public void setValueAt(Object value, int row, int col) //Don't need to implement this method unless your table's data can change.
TableDemo.java示意了如何自己創建並使用tableModel。
將表格添加到容器中
一般情況下我們都是將JTable放到JScrollPane中,從而使用它的滾動功能。JScrollPane會很貼心地把表頭放在表格上方,並在向下滑動時始終保持它可見。如果你就是不要用JScrollPane,那么記得在容器中添加表頭哦~就像下面這樣
container.setLayout(new BorderLayout()); container.add(table.getTableHeader(), BorderLayout.PAGE_START); container.add(table, BorderLayout.CENTER);
設置和更改列寬
設置列寬,直接上代碼:
TableColumn column = null; for (int i = 0; i < 5; i++) { column = table.getColumnModel().getColumn(i); if (i == 2) { column.setPreferredWidth(100); //third column is bigger } else { column.setPreferredWidth(50); } }
當你手動調整列寬時,其它列的寬度也會自動調整,因為窗體尺寸沒變。
用戶選擇
與JList一樣,JTable也支持三種選擇模式:
- 單獨選擇:SINGLE_SELECTION
- 單重連續選擇:SINGLE_INTERVAL_SELECTION
- 多重連續選擇:MULTIPLE_INTERVAL_SELECTION
具體來說,你還可以設置是否允許選擇行、選擇列或選擇單元格。需要注意的是,行、列選擇與單元格選擇會相互影響的。
1.在MULTIPLE_INTERVAL_SELECTION模式下:
選擇單元格被永遠禁止;選擇行、選擇列相互排斥,要么選擇若干行,要么選擇若干列;
2.在SINGLE_INTERVAL_SELECTION模式下:
禁止選擇單元格時,選擇行、選擇列相互排斥,要么選擇連續行,要么選擇連續列。
啟用選擇單元格時,三者必須同時被選中,此時單元格可被單獨或連續選中。
3.在SINGLE_SELECTION模式下:
禁止選擇單元格時,選擇行、選擇列相互排斥,要么選擇一行,要么選擇一列。
啟用選擇單元格時,三者必須同時被選中,每次只能選擇一個單元格。
注意,在JTable中無法同時選擇獨立的多個單元格,因為其Selection模型非常簡單,就是取行與列的交集。
對於已選擇的行、列的提取,使用JTable.getSelectedRows和
JTable.getSelectedRows來提取它們的index。而lead selection的提取則有點違反直覺,代碼如下:
String.format("Lead Selection: %d, %d. ",
table.getSelectionModel().getLeadSelectionIndex(),
table.getColumnModel().getSelectionModel().getLeadSelectionIndex());
監聽數據變更
為了監聽數據的變更,你需要調用model的addTableModelListener方法來添加一個監聽器,而這個監聽器必須實現了TableModelListener接口。該接口只有一個
void tableChanged(TableModelEvent e)方法,你需要在里面進行響應。TableModelEvent將提供必要的信息,來指示發生變更的位置。它的方法有:
int getColumn()//Returns the column for the event.
int getFirstRow()//Returns the first row that changed.
int getLastRow()//Returns the last row that changed.
int getType()//Returns the type of event - one of: INSERT, UPDATE and DELETE.
得到位置后,你就可以調用model的getValueAt方法來獲取最新值。
觸發數據變更事件
為了觸發數據變更事件,model必須知道如何創建數據變更事件。雖然這個過程很復雜,但是DefaultTableModel已經實現了。所以,你要么使用JTable默認的DefaultTableModel,要么自己繼承DefaultTableModel。吶,如果你認為DefaultTableModel不合適而自己繼承了 AbstractTableModel,那么你就得自己動動手啦!當數據被外部源改變時,你需要激活以下方法:
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.
渲染器與編輯器
渲染器決定了單元格內容的展現形式,而編輯器決定單元格內容被編輯的方式。比如,默認情況下,數字使用右對齊的JLabel來展示,布爾型變量使用單選控件來展示;而你在編輯某些列時,可能希望從下拉菜單中選擇內容,這就是編輯器的作用了。出於性能上的考慮,JTable並沒有為每一個單元格提供獨立的渲染器,而是根據數據類型來渲染的。JTable首先會檢查該列是否指定了渲染器,沒有的話就檢查該列的數據類型,並查看對應的渲染器。而基礎類型以外的對象基本都是調用其toString方法,並通過JLable來渲染的。編輯器也是一樣。
當然,我們可以對渲染器進行定制,來滿足特定的需求。對指定的列或指定的數據類型應用定制渲染器都可以。如果是前者你需要調用JTable的setDefaultRenderer方法;如果是后者你需要調用指定列的setCellRenderer方法。如果你要為特定的單元格指定渲染器,那你需要調用繼承JTable並重載getCellRenderer
方法。構造渲染器的最簡單方法是繼承DefaultTableCellRenderer類,然后實現它的setValue方法,你在里面使用setText或者setIcon來定制你渲染的內容。如果這還不夠,那你可以繼承一個已經存在的組件並實現 TableCellRenderer接口,比如讓你的JLable繼承這個接口,然后將它作為渲染器使用。
Table Render Demo Project是一個定制渲染器與編輯器的絕好例子,運行效果如下:
單元格提示(Tool tips for Cells)
單元格提示效果如上圖,鼠標懸停時可顯示出定制內容。在默認情況下,它是由單元格的渲染器決定的;然而,你也可以重載JTable的getToolTipText(MouseEvent)實現。下面分別介紹這兩種方法。
(1)為單元格渲染器添加提示,首先需要建立一個渲染器,確認它是一個JComponent,然后用它調用setToolTipText即可。以下代碼來自於TableRenderDemo.java
:
//Set up tool tips for the sport cells. DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); renderer.setToolTipText("Click for combo box"); sportColumn.setCellRenderer(renderer);
以下代碼來自於 ColorRenderer.java:
public class ColorRenderer extends JLabel implements TableCellRenderer { ... public Component getTableCellRendererComponent( JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { Color newColor = (Color)color; ... setToolTipText("RGB value: " + newColor.getRed() + ", " + newColor.getGreen() + ", " + newColor.getBlue()); return this; } }
(2)重載JTable的getToolTipText(MouseEvent)
在該方法中,找到指定列,然后返回指定的字符串。以下代碼來自於TableToolTipsDemo.java:
JTable table = new JTable(new MyTableModel()) { //Implement table cell tool tips. public String getToolTipText(MouseEvent e) { String tip = null; java.awt.Point p = e.getPoint(); int rowIndex = rowAtPoint(p); int colIndex = columnAtPoint(p); int realColumnIndex = convertColumnIndexToModel(colIndex); if (realColumnIndex == 2) { //Sport column tip = "This person's favorite sport to " + "participate in is: " + getValueAt(rowIndex, colIndex); } else if (realColumnIndex == 4) { //Veggie column TableModel model = getModel(); String firstName = (String)model.getValueAt(rowIndex,0); String lastName = (String)model.getValueAt(rowIndex,1); Boolean veggie = (Boolean)model.getValueAt(rowIndex,4); if (Boolean.TRUE.equals(veggie)) { tip = firstName + " " + lastName + " is a vegetarian"; } else { tip = firstName + " " + lastName + " is not a vegetarian"; } } else { //another column //You can omit this part if you know you don't //have any renderers that supply their own tool //tips. tip = super.getToolTipText(e); } return tip; } ... }
運行效果如下圖:
表頭提示
通常表頭中的不同列都具有不同的文字提示。你可以通過重載表頭的getToolTipText方法來改變文字提示,也可以調用TableColumn.setHeaderRenderer來為表頭提供一個定制的渲染器。
以下代碼來自於TableSorterDemo.java,它為所有列頭提供了相同的文字提示:
table.getTableHeader().setToolTipText(
"Click to sort; Shift-Click to sort in reverse order");
以下代碼來自於TableSorterDemo.java,它為后3列的列頭提供不同的文字提示。
protected String[] columnToolTips = { null, // "First Name" assumed obvious null, // "Last Name" assumed obvious "The person's favorite sport to participate in", "The number of years the person has played the sport", "If checked, the person eats no meat"}; ... JTable table = new JTable(new MyTableModel()) { ... //Implement table header tool tips. protected JTableHeader createDefaultTableHeader() { return new JTableHeader(columnModel) { public String getToolTipText(MouseEvent e) { String tip = null; java.awt.Point p = e.getPoint(); int index = columnModel.getColumnIndexAtX(p.x); int realIndex = columnModel.getColumn(index).getModelIndex(); return columnToolTips[realIndex]; } }; } };
運行效果如下:
排序和過濾