如何使用 Swing 組件 JTable
使用 JTable 類你可以顯示表中的數據,也可以允許用戶編輯表中的數據,JTable 不包含數據,也不緩存數據,它只是你的數據的壹個視圖,下圖是一個在滾動窗格中顯示的一個典型的表格:
本節的其余部分告訴你如何完成一些表格相關的常用任務。
創建壹個簡單的表格
1、點擊 Launch 可以使用 Java Web Start 運行 SimpleTableDemo ,或者由你自己來編譯運行 SimpleTableDemo.java ;2、點擊包含 "Snowboarding" 的單元格,這壹行都會被選中,說明你已經選擇了 Kathy Smith 的數據。壹個特殊的高亮顯示說明 "Snowboarding" 單元格是可以編輯的。通常來講,你可以通過雙擊單元格來編輯該單元的文本信息。
3、將鼠標指向 "First Name",現在點擊鼠標并且拖到右邊,如同你所看到的壹樣,用戶可以重新排列表格中的列名稱。
4、將鼠標指向列頭的右邊緣,現在點擊鼠標并且左右拖動,該列寬度發生了改變,并且其它列填充了多出來的空間。
5、調整包含表格的窗口大小,以便它比實際展示需要的空間更大壹些。所有的表格元素變寬了,水平的填充了多出來的額外空間。
SimpleTableDemo.java 中的表格在 String 數組中聲明列名稱:
String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};
它的數據被初始化并且存儲在壹個二維的對象數組中:
Object[][] data = { {"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false)}, {"John", "Doe", "Rowing", new Integer(3), new Boolean(true)}, {"Sue", "Black", "Knitting", new Integer(2), new Boolean(false)}, {"Jane", "White", "Speed reading", new Integer(20), new Boolean(true)}, {"Joe", "Brown", "Pool", new Integer(10), new Boolean(false)} };然后表格對象使用 data 和 columnNames 完成構建。
JTable table = new JTable(data, columnNames);
壹共有兩個 JTable 構造方法直接接受數據,SimpleTableDemo 使用第壹種:
JTable(Object[][] rowData, Object[] columnNames) JTable(Vector rowData, Vector columnNames)這些構造方法的優點就是它們非常易用,然而,這些構造方法也有它的缺點:
他們設置每個單元格可編輯;
他們以相同的方式對待所有的數據類型(全部識別為 String)。比如說,如果表中的某壹列包含 Boolean 數據,表格可以將該數據顯示為壹個復選框。因此,如果你使用上述兩個構造方法中任意壹個,你的 Boolean 數據都會被顯示為字符串。你可以在上圖中的 Vegetarian 列中查看這種差異。
他們要求你把所有的數據都存儲在壹個二維數組或者 Vector 對象中,但是這樣做對于某些情況下的數據是不合適的。比如說,如果你從數據庫中初始化壹系列數據,你可能希望直接查詢對象以獲取結果,而不是把所有的結果都復制到數組或者 Vector 中。
如果你希望繞過這些限制,你需要實現自己的 Table Model,可以參考 創建壹個 Table Model。
增加壹個表格到容器中
如下是壹段典型的創建壹個卷動窗格作為表格的容器的代碼:
JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true);上面兩行代碼做了如下事情:調用 JScrollPane 構造方法并傳入 table 對象作為其參數,這句話為 table 對象創建了壹個滾動窗格,table 被自動添加到該容器中;
滾動空格自動的將表頭放在了頂端,當表格中的數據滾動時仍然可以看到列名顯示在頂上。
如果你使用表格卻不包含滾動空格,那么你需要獲取表頭組件并且自己來放置它,比如說像下面這樣:
container.setLayout(new BorderLayout()); container.add(table.getTableHeader(), BorderLayout.PAGE_START); container.add(table, BorderLayout.CENTER);
設置和更改列寬度
默認情況下,表中所有的列開始時都是等寬的,并且所有的列會自動填充表格的整個寬度。當表格變寬或者變窄(當用戶調整包含表格的窗口時會發生),所有的列寬會自動適應變化。
當用戶通過拖動列的右邊緣來調整列寬度時,其它列必須改變寬度,或者表格的寬度也必須改變。默認情況下,表格的寬度會保持原樣,所有拖拽點右左邊的列會調整以適應右邊的空間增大,所有拖拽點左邊的列會調整以適應左邊的空間減少。
如果希望自定義初始的列寬,對于你的表格中的每壹列,你可以調用 setPreferredWidth 方法來進行設置。這會設置推薦寬度和他們的近似相對寬度。比如說:增加如下代碼到 SimpleTableDemo 會讓第三列比其它列要寬壹些。
TableColumn column = null; for (int i = 0; i < 5; i++) { column = table.getColumnModel().getColumn(i); if (i == 2) { column.setPreferredWidth(100); //第三列寬壹些 } else { column.setPreferredWidth(50); } }如同上述代碼顯示的壹樣,表格的每壹列都由壹個 TableColumn 對象表示。TableColumn 為列寬度的最小值,推薦值,最大值提供了 getter 和 setter 方法,同樣也提供了獲取當前列寬度的方法。比如說,基于壹個近似的空間設置需要繪制的單元格元素的寬度,可以參考 TableRenderDemo.java 的 initColumnSizes 方法。
當用戶明確的調整寬度時, 列的首選寬度設置為用戶指定的尺寸,同時變成列的當前寬度。然而,當表格自己調整時(通常因為窗口調整了)列的推薦寬度則不會改變,存在的首選寬度用于計算新的列寬以填充可用的空間。
你可以通過調用 setAutoResizeMode 方法改變表格的寬度調整行為。
用戶的選擇
在這個默認配置下,表格支持選擇壹行或者多行,用戶可以選擇連續的幾行。最后一個單元格指示的用戶得到一個特殊的跡象,在金屬的視覺上看,這個元素超出了輪廓,它有時被稱作焦點元素或者當前元素,用戶使用鼠標或者鍵盤來做出選擇,具體描述見下表:
操作 |
鼠標動作 |
鍵盤動作 |
選擇單行 |
單擊 |
上下光標鍵 |
延伸的連續選擇 |
Shift + 單擊 或者在表格行上拖動 |
Shift + 上下光標鍵 |
添加行選擇/切換行選擇 |
Ctrl + 單擊 |
移動控制向上箭頭或下箭頭選擇控制鉛,然后使用空格鍵添加到選擇或控制,空格鍵切換行選擇。 |
要查看選擇行是如何執行的,請點擊 Launch 以使用 Java? Web Start 來運行 TableSelectionDemo.java,或者由你自己來編譯運行這個 例子。
這個樣例程序展示了相似的表格,允許用戶操縱特定的 JTable 選項。它還有壹個文本面板記錄選擇事件。在下方的截圖中,壹個用戶運行程序,單擊第壹行,然后 Ctrl + 單擊第三行,注意點擊的最后壹個單元格周圍的輪廓,這就是突出率先選擇的金屬外觀和感覺。
在"Selection Mode"下方有幾個單選按鈕,點擊標簽是"Single Selection"的按鈕。現在你可以每次只選擇壹行了。如果你點擊標簽是"Single Interval Selection"的單選按鈕,你可以選擇連續的壹組行記錄。所有的"Selection Mode"下方的單選按鈕都調用了方法 JTable.setSelectionMode 這個方法只接受壹個參數,且必須是如下的定義在類 javax.swing.ListSelectionModel 中的常量值之壹:MULTIPLE_INTERVAL_SELECTION,SINGLE_INTERVAL_SELECTION 還有 SINGLE_SELECTION。
回到 TableSelectionDemo,注意在"Selection Options"下方的三個復選框,每個復選框控制壹個 boolean 型的由 JTable 定義的綁定變量:
"行選擇" 控制行選擇,對應的有兩個方法 setRowSelectionAllowed 和 getRowSelectionAllowed。當這個綁定屬性為 true,并且 ColumnSelectionAllowed 的屬性值為 false 時,用戶可以選擇行記錄;
"列選擇" 控制列選擇,對應的有兩個方法 setColumnSelectionAllowed 和 getColumnSelectionAllowed。當這個綁定屬性為 true,并且 rowSelectionAllowed 綁定屬性為 false 時,用戶可以選擇列記錄;
"單元格選擇" 控制單元格選擇,對應的有兩個方法 setCellSelectionEnabled 和 getCellSelectionEnabled。當這個綁定屬性為 true 時,用戶可以選擇單個單元格或者矩形塊狀的單元格;
如果你清除所有的三個復選框,將三個綁定屬性設置為 false,那麼就不會有選擇。只有高亮元素顯示。
你可能會注意到在間隔選擇模式下 "Cell Selection" 復選框被禁用。這是因為在演示中,不支持這種模式下的單元格選擇。你可以在間隔選擇模式下指定單元格的選擇,但是結果集是壹個不會產生任何有效選擇的表格。
你同樣可能注意到更改任意三個選項都會影響到其它選項,這是因為同時允許行選擇和列選擇和允許單元格選擇完全壹樣,JTable 自動更新了三個綁定變量以保持它們的壹致性。
注意:設置 cellSelectionEnabled 到某個值會對 rowSelectionEnabled 和 columnSelectionEnabled 有邊緣影響,它們也會被設置為這個值。同時設置 rowSelectionEnabled 和 columnSelectionEnabled 到某個值同樣對 cellSelectionEnabled 有邊緣影響,它也會被設置為那個值。設置 rowSelectionEnabled 和 columnSelectionEnabled 為不同的值會對 cellSelectionEnabled 有邊緣影響,它會被設置為 false。
為了獲取當前選擇,使用 JTable.getSelectedRows 會返回選擇的所有行下標,使用 JTable.getSelectedColumns 會返回所有的列下標。 要檢索率先選擇的坐標,參照表和表的列模型的選擇模型。下面的代碼格式化包含率先選擇的行和列的字符串:
String.format("Lead Selection: %d, %d. ", table.getSelectionModel().getLeadSelectionIndex(), table.getColumnModel().getSelectionModel().getLeadSelectionIndex());
使用選擇功能生成壹系列的事件。請參考 編寫事件監聽器 課程的 如何編寫壹系列選擇事件監聽器 這壹節。
注意:選擇數據實際描述為在視圖中選擇單元格 (表格數據就像它排序或者過濾之后顯示出來的壹樣) 而不是選擇表格的 Model。這個區別不會影響你,除非你查看重新排列的數據,包括排序,過濾或者用戶操作過的行。在這種情況下,你必須使用在排序和過濾中提到的轉換方法轉換選擇坐標。
創建壹個 Table Model
每個表對象使用壹個 table model 對象來管理實際的表數據。壹個 table model 對象必須實現 TableModel 接口。如果開發人員不提供 table model 對象,那么 JTable 會自動創建壹個 DefaultTableModel 類的實例 這種關系如下圖所示。
用 SimpleTableDemo 作為 table model 構造 JTable 表格的示例代碼如下:
new AbstractTableModel() { public String getColumnName(int col) { return columnNames[col].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 col) { return true; } public void setValueAt(Object value, int row, int col) { rowData[row][col] = value; fireTableCellUpdated(row, col); } }如同上述代碼所示,實現壹個 table model 可以很簡單。通常來講,你可以在 AbstractTableModel 的子類里來實現你自己的 Table Model。你的 model 可能把數據保存在 Array,Vector 或者哈希表中,或者它也可以從外部源獲取數據,比如說從數據庫獲取數據。它甚至可以在運行期間生成表數據。
這個表格與 SimpleTableDemo 在如下幾個地方存在不同點:
1、TableDemo 的自定義 table model 哪怕很簡單,它也可以決定數據的類型,幫助 JTable 以更好的格式展示數據。從另壹方面講,SimpleTableDemo 的自動創建的 table model,不知道列 # of Years 包含靠右對齊的數字,并且具有特殊的格式。它不知道列 Vegerarian 包含布爾型值,可以將其展示成復選框。
2、在 TableDemo 中實現的自定義 table model 不允許你編輯姓名列,然而它允許你編輯其它列。在 SimpleTableDemo 中,所有的元素都是可以編輯的。
可以看到來自 TableDemo.java 的如下代碼和來自 SimpleTableDemo.java 的不壹樣。黑體字顯示創建表格的 model 與為創建 SimpleTableDemo 而自動生成的 table model 不壹樣。
public TableDemo() { ... JTable table = new JTable(new MyTableModel()); ... } class MyTableModel extends AbstractTableModel { private String[] columnNames = ...//和以前壹樣 private Object[][] data = ...//和以前壹樣 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]; } public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } //如果你的表格不可編輯就不要實現這個方法 public boolean isCellEditable(int row, int col) { //請注意單元格的地址是常量,無論這個單元格在屏幕的什么地方出現 if (col < 2) { return false; } else { return true; } } //如果你的表格中的數據不改變,則不需要實現這個方法 public void setValueAt(Object value, int row, int col) { data[row][col] = value; fireTableCellUpdated(row, col); } ... }
監控數據的更新
當表格中的數據發生改變時,每個 table model 都可以有壹系列的監聽器,監聽器都是 TableModelListener 的實例。在如下代碼中,SimpleTableDemo 繼承了這樣壹個監聽器。
import javax.swing.event.*; import javax.swing.table.TableModel; public class SimpleTableDemo ... implements TableModelListener { ... public SimpleTableDemo() { ... table.getModel().addTableModelListener(this); ... } public void tableChanged(TableModelEvent e) { int row = e.getFirstRow(); int column = e.getColumn(); TableModel model = (TableModel)e.getSource(); String columnName = model.getColumnName(column); Object data = model.getValueAt(row, column); ...//對數據做壹些事情... } ... }
發起數據更新事件
為了發起數據更新的事件,table model 必須知道如何創建壹個 TableModelEvent 對象。這可以是壹個復雜的過程,但是它現在已經在 DefaultTableModel 中實現了。你既可以允許 JTable 使用默認的DefaultTableModel 實例,也可以創建你自己的 DefaultTableModel 的自定義的子類。
如果 DefaultTableModel 對于你自定義的 table model 類而言不適用,你就可以考慮繼承 AbstractTableModel 類實現壹個子類。這個類為創建 TableModelEvent 對象實現了壹個簡單的框架。你的自定義類只需要在每次表數據被外部源改變時,簡單的調用如下的 AbstractTableModel 方法。
方法名 |
發生的變化 |
fireTableCellUpdated |
更新了某個單元格 |
fireTableRowsUpdated |
更新了某些行記錄 |
fireTableDataChanged |
更新了整個表格中的數據 |
fireTableRowsInserted |
插入了新的行記錄 |
fireTableRowsDeleted |
存在的行被刪除了 |
fireTableStructureChanged |
整個表格失效,包括數據和結構 |
相關內容:編輯器和渲染器
在你開始繼續接下來幾個任務之前,你需要理解表格是如何畫出它們的單元格。你可能期望每個表格中的單元格都是壹個組件。然而,出于性能方面考慮,Swing 的表格的實現是不壹樣的。
相反,單獨壹個單元格渲染器通常可以用來畫出所有的包含相同數據類型的單元格。你可以將渲染器想象成壹個可配置的墨水印章,表格使用它來給適當格式化的數據蓋章。當用戶開始編輯單元格中的數據時,壹個單元格渲染器接手單元格,控制單元格的編輯行為。
舉例來說,TableDemo 中的每個# of Years 列的單元格包含 Number 型數據,比較特殊,是 Integer 對象。默認情況下,單元格渲染器對于壹個包含數字的列,在每個列中的單元格上都使用壹個 JLable 實例來畫出合適的數字,靠右對齊。如果用戶開始編輯其中壹個單元格,默認的單元格編輯器使用靠右對齊的 JTextField 來控制單元格的編輯。
為了選擇渲染器來顯示列中的單元,你首先需要決定表格是否需要為特定的列使用特定的渲染器。如果不需要,那么表格調用 table model 的 getColumnClass 方法,后者將會獲取列中元素的數據類型,下壹步,表格中列的數據類型與壹個數據類型列表比較,數據類型列表中的元素渲染器已經注冊過。這個列表由表格來完成初始化,但是你可以增加或者改變它。目前,表格將如下類型的數據放進列表中:
Boolean — 被渲染成壹個復選框; Number — 被渲染成壹個靠右對齊的標簽; Double, Float — 和 Number 類型壹樣,但是壹個 NumberFormat 實例會完成對象到文本的轉換,針對當前語言使用默認的數字格式; Date — 被渲染成壹個標簽,但是壹個 DateFormat 實例會完成對象到文本的轉換,針對時間和日期使用壹種簡短的風格; ImageIcon, Icon — 被渲染成壹個居中對齊的標簽; Object — 被渲染成壹個顯示對象的字符串值的標簽;
單元格編輯器的選擇使用類似的算法。
記住,如果你允許壹個表格使用它自己的 model ,它就會使用 Object 作為每個列的類型。為了指定更加精確的列類型,table model 必須定義恰當的 getColumnClass() 方法。就像 TableDemo.java 的演示代碼那樣。牢記盡管渲染器決定每個單元格或者列頭部如何展示,以及它們的 tool tip 文本是什么,但是渲染器不處理事件。如果你需要獲取發生在表格中的事件,你需要使用技術不同的你感興趣的事件排序:
情況 |
如何獲取事件 |
檢測單元格被編輯的事件 |
使用單元格編輯器,或者在單元格編輯器上注冊壹個監聽器 |
檢測行、列或者單元格的選擇與反選事件 |
使用 Detecting User Selections 中提到的選擇監聽器 |
在列的頭部檢測鼠標事件 |
在表格的 JTableHeader 對象中注冊 mouse listener 的合適類型。請查閱 TableSorter.java 中的實例。 |
檢測其它事件 |
為 JTable 對象注冊合適的監聽器 |
使用自定義的渲染器
這壹章節告訴我們如何創建和指定壹個單元格的渲染器。你可以使用 JTable 的方法 setDefaultRenderer() 來設置壹個指定類型的單元格渲染器。為了讓某個特殊列里面的單元格使用指定的渲染器,你可以使用 TableColumn 的 setCellRenderer() 方法。你甚至可以通過創建 JTable 的子類來指定壹個特定于單元格的渲染器。
使用 DefaultTableCellRenderer 可以很容易的自定義文本或者圖片的默認渲染器,你只需要創建壹個子類并且實現 setValue() 方法以便它可以使用合適的字符串或者圖片來調用 setText() 或者 setIcon() 方法。舉個例子,這里是壹個默認的日期類渲染器的實現:
static class DateRenderer extends DefaultTableCellRenderer { DateFormat formatter; public DateRenderer() { super(); } public void setValue(Object value) { if (formatter==null) { formatter = DateFormat.getDateInstance(); } setText((value == null) ? "" : formatter.format(value)); } }
如果擴展 DefaultTableCellRenderer 還不夠,你可以使用另外壹個子類創建壹個渲染器。最簡單的方法就是創建壹個已經存在的組件的子類,實現 TableCellRenderer 接口來創建你自己的子類。TableCellRenderer 只需要實現壹個方法:getTableCellRendererComponent()。你對這個方法的實現應該設置渲染組件,以反映傳入的狀態,然后返回組件。
在 TableDialogEditDemo 的截圖中,用于 Favorite Color 單元格上的渲染器是壹個 JLabel 的子類,被稱作 ColorRenderer。這里有壹個來自于 ColorRenderer.java 的片段用于展示它是如何實現的。
public class ColorRenderer extends JLabel implements TableCellRenderer { ... public ColorRenderer(boolean isBordered) { this.isBordered = isBordered; setOpaque(true); //MUST do this for background to show up. } public Component getTableCellRendererComponent( JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { Color newColor = (Color)color; setBackground(newColor); if (isBordered) { if (isSelected) { ... //selectedBorder is a solid border in the color //table.getSelectionBackground(). setBorder(selectedBorder); } else { ... //unselectedBorder is a solid border in the color //table.getBackground(). setBorder(unselectedBorder); } } setToolTipText(...); //會在后面討論 return this; } }這里是來自 TableDialogEditDemo.java 的壹段代碼注冊了壹個 ColorRenderer 實例作為所有顏色的數據的默認渲染器:
table.setDefaultRenderer(Color.class, new ColorRenderer(true));為了給特定單元格指定渲染器,你需要定義壹個 JTable 子類,重載 getCellRenderer() 方法。舉個例子,下述的代碼讓表格中的第壹列的第壹個單元格使用特定的渲染器:
TableCellRenderer weirdRenderer = new WeirdRenderer(); table = new JTable(...) { public TableCellRenderer getCellRenderer(int row, int column) { if ((row == 0) && (column == 0)) { return weirdRenderer; } // else... return super.getCellRenderer(row, column); } };
為單元格指定工具提示信息
默認情況下,顯示在表中單元格上的工具提示文本由單元格的渲染器決定。然而,有時候通過重載 JTable 的實現中的 getToolTipText(MonseEvent e) 方法可以更加簡單指定工具提示文本。這壹章節向你展示如何使用這兩個技術。
為了在壹個單元格的渲染器上增加工具提示,你首先需要獲得或者創建壹個單元格渲染器。然后,確保渲染器組件是壹個 JComponent,調用它的 setToolTipText() 方法即可。
TableRenderDemo 中有壹個為單元格設置工具提示的例子。點擊這里可以運行該樣例代碼,當然你也可以自己編譯運行它的源代碼。它使用如下代碼在 Sport 列中為單元格添加了工具提示:
DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); renderer.setToolTipText("Click for combo box"); sportColumn.setCellRenderer(renderer);
雖然上個例子里的工具提示文本樣例是靜態的,但是你也可以實現隨著單元格狀態改變而改變的工具提示文本程序,這里有兩種實現的方法:
1、在渲染器的 getTableCellRendererComponent() 方法實現中增加壹些代碼;
2、重載 JTable 的 getToolTipText(MonseEvent e)方法;
壹個在單元格渲染器中添加代碼的例子是 TableDialogEditDemo。點擊啟動按鈕可以運行這個例子,或者你也可以自行編譯運行這個例子。TableDialogEditDemo 使用了壹個用于顏色的渲染器,并且在 ColorRender.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; } }下圖是工具欄提示運行時的效果:
通過重載 JTable 的 getToolTipText(MouseEvent e) 方法你可以指定工具欄提示文本,程序 TableToolTipsDemo 展示了該如何進行。Sport 和 Vegetarian 兩個列中的元素具有工具欄提示,它們的演示效果如下:
這是來自 TableToolTipsDemo 中的壹段代碼,它實現了在 Sport 和 Vegetarian 列中的元素上添加工具欄提示的功能。
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; } ... }該代碼是相當簡單的,除非是調用convertColumnIndexToModel()。該調用是必要的,因為如果用戶移動周圍的列,列的視圖的索引將不匹配模型的索引列。例如,用戶可以拖動 Vegetarian 列(假設該模型的列在索引4),使得它可以顯示作為第一列 - 此時視圖索引為0。由于prepareRenderer提供視圖索引,您需要轉換視圖索引到模型索引,這樣你才可以確信該列已選定。
為表頭指定工具提示信息
通過為你的表格的 JTableHeader設置工具提示文本,你可以增加壹個工具提示到列的頭部。通常不同的列頭部需要不同的工具提示信息。通過重載表格頭的 getToolTipText() 方法你可以改變文本信息。你可以交替的調用 TableColumn.setHeaderRenderer() 方法來為表頭提供自定義的渲染器。
在 TableSorterDemo.java 中有壹個為所有列的頭部使用工具提示文本的例子,如下是如何設置工具提示信息的代碼:
table.getTableHeader().setToolTipText("Click to sort; Shift-Click to sort in reverse order");TableToolTipsDemo.java 中實現了壹個根據列的頭部給出不同的工具提示信息的例子。除了最初的兩個以外,當你的鼠標劃過任何列的頭部時,你會看見工具提示信息。因為他們似乎不言自明,所以沒有為這些名稱列提供工具提示信息。
接下來的代碼實現了工具提示功能,基本上,它創建了壹個 JTableHeader 的子類并且重載了 getToolTipText(MonseEvent e)方法,以便它可以為當前列返回文本。為了關聯修改表的表頭,JTable 中的 createDefaultTableHeader() 方法被重載了,以便它返回壹個 JTableHeader 子類的實例。
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()) { ... //實現表頭工具欄提示 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]; } }; } };
排序與過濾數據
表格的排序和過濾由壹個 sorter 對象進行管理,最簡單的提供 sorter 對象的方法是設置 autoCreateRowSorter 綁定屬性為 true:
JTable table = new JTable(); table.setAutoCreateRowSorter(true);
這次我們定義壹個行排序 sorter,它是 javax.swing.table.TableRowSorter 的壹個實例。當用戶點擊列的頭部時,排序工具提供給表格壹個簡單的基于當地的排序。TableSortDemo.java 就是這樣壹個演示的例子,下圖是截屏:
要想獲取更多關于排序的控制,你可以創建壹個 TableRowSorter 的實例并且指定它是你的表格中的排序對象。
TableRowSorterTableRowSorter 使用 java.util.Comparator 對象來完成行排序。壹個實現了這樣接口的類提供壹個 compare() 方法用于定義任意兩個對象為了完成排序應該如何進行比較。舉例來說,下述的代碼創建了壹個比較器,用于對比壹系列的字符串通過對比每個字符串最后壹個字母來進行排序:sorter = new TableRowSorter (table.getModel()); table.setRowSorter(sorter);
Comparator這個例子是特意簡化過的,更有代表性的,壹個比較器的實現是壹個 java.text.Collator 的子類。你可以定義你自己的子類,使用在 Collator 類中的工廠方法來根據指定的語言獲得壹個 Comparator 對象,或者使用 java.text.RuleBasedCollator 類。comparator = new Comparator () { public int compare(String s1, String s2) { String[] strings1 = s1.split("\\s"); String[] strings2 = s2.split("\\s"); return strings1[strings1.length - 1] .compareTo(strings2[strings2.length - 1]); } };
這個例子是特地簡化過的;更常見情況下,Comparator 的實現是 java.text.Collator 的壹個子類。你可以有你自己的子類,使用在 Collator 類的工廠方法可以獲取壹個基于本地環境的 Comparator 實例,或者使用 java.text.RuleBasedCollator 。
對于每壹列應該使用哪種 Comparator ,TableRowSorter 會嘗試應用如下規則。規則會按照如下的順序被遵循;第壹條規則如果已經使用,則后繼的規則會被忽略。
1、如果已經通過 setComparator() 指定了壹個比較器,則使用該比較器。 2、如果 table model 已經聲明列數據由字符串組成,就是說對于指定列調用 TableModel.getColumnClass() 方法返回 String.class,使用基于本地環境的字符串順序進行排序。 3、如果針對列調用 TableModel.getColumnClass() 之后的返回值實現了 Comparable 接口,則使用 Comparable.compareTo() 方法的返回值的字符串順序進行排序。 4、如果表格調用了 setStringConverter() 方法指定了字符串轉換器,則使用基于本地環境的字符串的表現形式的字符串排序器排序。 5、如果以上規則均不適用,則先調用列中數據的 toString() 方法將其轉換成字符串類型,然后針對轉換后的字符串使用基于本地環境的字符串順序進行排序。
對于更多更加復雜的排序類型,可以繼承 TableRowSorteror 的父類 javax.swing.DefaultRowSorter 。
為調用 setSortKeys() 可以指定列的排序順序和排序優先級。這里有壹個針對表格的前兩列進行排序的例子。優先的列可以在排序鍵的列表中指定。在這種情況下,第二列擁有第壹個排序鍵,所以這些行先按照 first name 排序,然后再按照 second name 排序。
ListsortKeys = new ArrayList (); sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING)); sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING)); sorter.setSortKeys(sortKeys);
除了重新排序的結果,也可以指定壹個表分揀機指定顯示哪些行,這就是所謂的過濾。TableRowSorter 使用 javax.swing.RowFilter 對象實現過濾。RowFilter 實現了幾個工廠方法用于創建常用的過濾器類型。舉例來說,regexFilter 返回壹個基于正則表達式過濾的 RowFilter。
在如下的樣例代碼中,你明確的創建壹個排序器對象以便你可以在稍后使用它作為過濾器。
MyTableModel model = new MyTableModel(); sorter = new TableRowSorter(model); table = new JTable(model); table.setRowSorter(sorter);
然后你可以基于文本域的當前值過濾:
private void newFilter() { RowFilterrf = null; try { rf = RowFilter.regexFilter(filterText.getText(), 0); } catch (java.util.regex.PatternSyntaxException e) { return; } sorter.setRowFilter(rf); }
在后面的例子中,每次文本域改變時都會調用 newFilter() 方法。當用戶輸入難懂的正則表達式時,try...catch 會防止干擾和阻止語法端的輸入異常。當表格使用了排序器,用戶看到的數據可能呈現壹種與 table model 指定的不同的順序,也有可能包含 table model 所指定的所有數據。用戶實際看到的數據被稱作視圖,它有自己的坐標系。JTable 提供了從 model 坐標系到視圖坐標系的轉換方法: convertColumnIndexToView() 和 convertRowIndexToView() ,也提供了從視圖坐標系到 model 坐標系的轉換方法:convertColumnIndexToModel() 和 convertRowIndexToModel()。
注意:當使用排序器時,總要記住轉換單元格的坐標系。接下來的例子把我們這節討論過的所有想法都帶來了。TableFilterDemo.java 給 TableDemo 做了壹些小的改變。它們包含在這節之前的代碼片段中,作用是為主表提供了壹個排序器,并且使用壹個文本框來提供過濾用的正則表達式。如下的截屏展示了 TableFilterDemo 在排序和過濾操作之前的樣子。注意模型中的第三行在視圖中也在相同的第三行:
如果用戶在第二列上點擊兩次,第四列就變成了第壹列,但只是在視圖中:
就像之前提到的那樣,用戶在"Filter Text"中輸入的文本決定哪些行會顯示,同樣對于排序,過濾引起視圖坐標系偏離模型坐標系:
這里是更新狀態文本來映射當前選擇的代碼:
table.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { int viewRow = table.getSelectedRow(); if (viewRow < 0) { statusText.setText(""); } else { int modelRow = table.convertRowIndexToModel(viewRow); statusText.setText(String.format("Selected Row in view: %d. " + "Selected Row in model: %d.", viewRow, modelRow)); } } } );
使用 ComboBox 下拉列表作為編輯器
啟用壹個下拉列表作為編輯器很容易,如同下述樣例展示的壹樣。
TableColumn sportColumn = table.getColumnModel().getColumn(2); ... JComboBox comboBox = new JComboBox(); comboBox.addItem("Snowboarding"); comboBox.addItem("Rowing"); comboBox.addItem("Chasing toddlers"); comboBox.addItem("Speed reading"); comboBox.addItem("Teaching high school"); comboBox.addItem("None"); sportColumn.setCellEditor(new DefaultCellEditor(comboBox));
這是下拉列表在使用時的效果:
上述代碼來自于 TableRenderDemo.java 。
使用其它編輯器
無論你是為單個的列或者單元格設置編輯器(使用TableColumnsetCellEditor的方法)還是為特定的數據類型設置編輯器(使用JTablesetDefaultEditor的方法),你都應該讓這個編輯器實現 TableCellEditor 接口。幸運的是,DefaultCellEditor 類實現這個接口,并允許你指定編輯組成部分是一個JTextField,JCheckBox 或者 JComboBox 的構造函數。通常,您不需要明確指定一個復選框作為壹個編輯器,因為布爾數據的列會自動使用復選框渲染器和編輯器。
如果你想指定復選框、下拉列表、文本框編輯器以外的文本字段該如何做呢?由于DefaultCellEditor不支持其他類型的組件,您必須做更多一點的工作。您需要創建一個類實現 TableCellEditor 的接口。 AbstractCellEditor 類是一個很好的超類使用。它實現了 TableCellEditor 的超級接口,CellEditor,減少了您的麻煩同時實現了事件觸發單元格編輯器所必需的代碼。
你的單元格編輯器需要定義至少兩個方法:getCellEditorValue() 和 getTableCellEditorComponent()。 方法 getCellEditorValue() 的定義是 CellEditor 所必需的,它返回單元格的當前值。方法 getTableCellEditorComponent() 則是 TableCellEditor 所必需的,應該配置為返回你希望使用的那個編輯器的組件。
這里有壹個用對話框作為單元格編輯器(不是直接的出現)的表格的圖片。當用戶開始編輯列 Favorite Color 列中的單元格時,壹個按鈕(真正的單元格編輯器)出現并且顯示出對話框,在對話框中用戶可以選擇壹個不壹樣的顏色值。

上述代碼參見 TableDialogEditDemo.java 。
這是來自 ColorEditor.java 的源代碼,它實現了單元格編輯器。
public class ColorEditor extends AbstractCellEditor implements TableCellEditor, ActionListener { Color currentColor; JButton button; JColorChooser colorChooser; JDialog dialog; protected static final String EDIT = "edit"; public ColorEditor() { button = new JButton(); button.setActionCommand(EDIT); button.addActionListener(this); button.setBorderPainted(false); //Set up the dialog that the button brings up. colorChooser = new JColorChooser(); dialog = JColorChooser.createDialog(button, "Pick a Color", true, //模型 colorChooser, this, //確認按鈕的 handler null); //沒有取消按鈕的 handler } public void actionPerformed(ActionEvent e) { if (EDIT.equals(e.getActionCommand())) { //用戶點擊了單元格,所以觸發了彈出對話框 button.setBackground(currentColor); colorChooser.setColor(currentColor); dialog.setVisible(true); fireEditingStopped(); //讓渲染器重新可見 } else { //用戶點擊了對話框中的 OK 按鈕 currentColor = colorChooser.getColor(); } } //實現了壹個 AbstractCellEditor 沒有實現的 CellEditor 方法 public Object getCellEditorValue() { return currentColor; } //實現了 TableCellEditor 定義的壹個方法 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { currentColor = (Color)value; return button; } }就像你看到的壹樣,代碼很簡單。略有點棘手的地方是在編輯器按鈕動作處理程序結束的時候需要調用 fireEditingStopped() 方法。沒有這個調用,編輯器會仍然保持活動狀態,盡管模態對話框已經不再可見。通過調用 tofireEditingStopped() 方法可以讓表格知道它可以關閉編輯器,讓單元格可以再次被渲染器處理。
使用編輯器驗證用戶輸入的文本信息的合法性
如果一個單元格的默認編輯器允許文本輸入,如果單元格的類型指定為字符串或對象以外的東西,你會得到一些免費的錯誤檢查。錯誤檢查的副作用是將輸入的文字轉換成一個對象的正確類型。
自動檢查用戶輸入的字符串時發生在默認編輯器試圖創建一個新的單元格的列相關聯的類的實例的時候。默認編輯器使用一個String作為參數的構造函數創建這個實例。例如,在一列Integer類型的單元格中,當用戶輸入“123”的默認編輯器創建相應的整數,使用相當于新的整數(“123”)的代碼。如果構造函數拋出異常,單元格的輪廓變成紅色,并拒絕讓焦點移動的單元格。如果實現列的數據類型作為一類,你可以使用默認的編輯器,如果你的類提供了一個接受一個參數類型為String的構造函數。
如果你想使用壹個文本框作為單元格的編輯器,但是想自定義它 — 可能需要更加嚴格的檢查用戶輸入,或者當輸入文本不合法時用不同的方法重構 — 你可以使用壹個 formatted text field 改變單元格編輯器。用戶表明打字(如按Enter鍵)結束之后,格式化的文本框可以連續檢查用戶鍵入后的值。
接下來的代碼來自于 TableFTFEditDemo.java ,它設置了壹個格式化文本框作為編輯器,后者限制所有的整型值都必須在 0 到 100 之間。接下來的代碼為所有的列創建了壹個包含 Integer 類型數據的格式化文本框:table.setDefaultEditor(Integer.class, new IntegerEditor(0, 100));
IntegerEditor 類是作為 DefaultCellEditor 的壹個子類實現的,并且使用了壹個 JFormattedTextField 代替了 JTextField,后者是由 DefaultCellEditor 支持的。它是通過首先設置壹個使用整型格式的格式化文本框,并且指定最小和最大值,然后使用 How to Use Formatted Text Fields 中提到的 API 實現的。之后它重載了 DefaultCellEditor 的getTableCellEditorComponent(),getCellEditorValue(), 和 stopCellEditing() 方法實現,為格式化文本框增加了必要的操作。
覆蓋getTableCellEditorComponent()設置格式的文本字段的值屬性(不僅僅是文本的屬性,它繼承自JTextField的)編輯器顯示之前。覆蓋getCellEditorValue()保持作為一個整數單元格的值,而不是,比方說,Long值,格式化文本字段的解析器趨于恢復。最后,覆蓋stopCellEditing()讓你檢查文本是否是有效的,可能停止編輯被解雇。如果文本是無效的,你stopCellEditing()的實施提出了一個對話框,讓用戶選擇繼續編輯或恢復到最后一個良好的價值。源代碼是太長了一點,包括在這里,你也可以查看 IntegerEditor.java 。
打印表格內容
JTable 提供了打印表格的壹個簡單的 API。最簡單的打印出表格的方法就是不帶參數調用 JTable.print() 。
try { if (! table.print()) { System.err.println("User cancelled printing"); } } catch (java.awt.print.PrinterException e) { System.err.format("Cannot print %s%n", e.getMessage()); }
在壹個正常的 Swing 應用上調用打印功能會打開壹個標準的打印對話框。在壹個沒有表格頭的應用中,則簡單的打印它。它的返回值表示用戶是否繼續打印的任務還是放棄打印。JTable.print() 可以拋出 java.awt.print.PrinterException 異常,它是壹個 受檢異常 ,這就是為什么我們在例子中要使用壹個 try ... catch 語句來包圍它。
JTable 提供了幾個 print() 的重載方法,可以使用各種各樣的選項來完成打印。如下來自 TablePrintDemo.java 的代碼展示了如何定義頁面頭部:
MessageFormat header = new MessageFormat("Page {0,number,integer}"); try { table.print(JTable.PrintMode.FIT_WIDTH, header, null); } catch (java.awt.print.PrinterException e) { System.err.format("Cannot print %s%n", e.getMessage()); }如果想了解更多更復雜的表格打印功能,可以使用 JTable.getPrintable() 來從表格中獲取壹個 Printable 對象。 關于 Printable 的更多信息,你可以參考 Printing 這壹節課程,它出現在 2D Graphics 系列課程中。
本文的英文原文來自http://docs.oracle.com/javase/tutorial/uiswing/components/table.html ,中文翻譯文章首發開源中國社區 http://my.oschina.net/bairrfhoinn/blog/166850 ,轉載請注明原始出處。