[翻譯]如何使用 Swing 組件 JTable

使用 JTable 類你能夠顯示錶中的數據,也能夠容許用戶編輯表中的數據,JTable 不包含數據,也不緩存數據,它只是你的數據的壹個視圖,下圖是一個在滾動窗格顯示的一個典型的表格 html

本節其他部分告訴如何完成一些表格相關的經常使用任務
java

建立壹個簡單的表格

一、點擊 Launch 可使用 Java Web Start 運行 SimpleTableDemo ,或者由你本身來編譯運行 SimpleTableDemo.java ;
二、點擊包含 "Snowboarding" 的單元格,這壹行都會被選中,說明你已經選擇了 Kathy Smith 的數據。壹個特殊的高亮顯示說明 "Snowboarding" 單元格是能夠編輯的。一般來說,你能夠經過雙擊單元格來編輯該單元的文本信息。
三、將鼠標指向 "First Name",如今點擊鼠標而且拖到右邊,如同你所看到的壹樣,用戶能夠從新排列表格中的列名稱。
四、將鼠標指向列頭的右邊緣,如今點擊鼠標而且左右拖動,該列寬度發生了改變,而且其它列填充了多出來的空間。
五、調整包含表格的窗口大小,以便它比實際展現須要的空間更大壹些。全部的表格元素變寬了,水平的填充了多出來的額外空間。

SimpleTableDemo.java 中的表格在 String 數組中聲明列名稱:
String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};

它的數據被初始化而且存儲在壹個二維的對象數組中: web

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 被自動添加到該容器中;
調用 JTable.setFillsViewportHeight() 設置了fillsViewportHeight 屬性,設置表格是否須要大到足以填充封閉視圖的所有高度。當這個屬性爲 true 時,表格對象使用容器的全部高度,哪怕沒有足夠多的行來使用垂直的空間,這樣作能夠更加容易的使用表格做爲拖拽的目標。
滾動空格自動的將表頭放在了頂端,當表格中的數據滾動時仍然能夠看到列名顯示在頂上。
若是你使用表格卻不包含滾動空格,那麼你須要獲取表頭組件而且本身來放置它,好比說像下面這樣:
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(int) 方法改變表格的寬度調整行爲。

用戶的選擇

在這個默認配置下,表格支持選擇壹行或者多行,用戶能夠選擇連續的幾行。最後一個單元格指示的用戶獲得一個特殊的跡象,在金屬的視覺上看,這個元素超出了輪廓,它有時被稱做焦點元素或者當前元素,用戶使用鼠標或者鍵盤來作出選擇,具體描述見下表: api

操做    
鼠標動做
鍵盤動做
選擇單行
單擊    
上下光標鍵
延伸的連續選擇
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_SELECTIONSINGLE_INTERVAL_SELECTION 還有 SINGLE_SELECTION緩存

回到 TableSelectionDemo,注意在"Selection Options"下方的三個複選框,每個複選框控制壹個 boolean 型的由 JTable 定義的綁定變量: oracle

"行選擇" 控制行選擇,對應的有兩個方法 setRowSelectionAllowed() 和 getRowSelectionAllowed()。當這個綁定屬性爲 true,而且 columnSelectionAllowed 的屬性值爲 false 時,用戶能夠選擇行記錄;
"列選擇" 控制列選擇,對應的有兩個方法 setColumnSelectionAllowed() 和 getColumnSelectionAllowed()。當這個綁定屬性爲 true,而且 rowSelectionAllowed 綁定屬性爲 false 時,用戶能夠選擇列記錄;
"單元格選擇" 控制單元格選擇,對應的有兩個方法 setCellSelectionEnabled() 和 getCellSelectionEnabled()。當這個綁定屬性爲 true 時,用戶能夠選擇單個單元格或者矩形塊狀的單元格;

注意:JTable 使用很是簡單的選擇概念管理行和列的交叉點, 不是設計來處理徹底獨立單元格選擇的
若是你清除全部的三個複選框,將三個綁定屬性設置爲 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 在以下幾個地方存在不一樣點:
一、TableDemo 的自定義 table model 哪怕很簡單,它也能夠決定數據的類型,幫助 JTable 以更好的格式展現數據。從另壹方面講,SimpleTableDemo 自動建立的 table model 並不知道列 # of Years 包含靠右對齊的數字,而且具備特殊的格式。它不知道列 Vegerarian 包含布爾型值,能夠將其展現成複選框。
二、在 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) {
        return col >= 2;
    }

    //若是你的表格中的數據不改變,則不須要實現這個方法
    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(int row, int column)
更新了某個單元格,其座標爲(row, column)
fireTableRowsUpdated(int firstRow, int lastRow)
更新了從 firstRow 到 lastRow 的行記錄,包括 firstRow 和 lastRow
fireTableDataChanged()
更新了整個表格中的數據
fireTableRowsInserted(int firstRow, int lastRow)
在行的範圍[firstRow, lastRow]內已經插入新的數據,包含 firstRow 和 lastRow
fireTableRowsDeleted(int firstRow, int lastRow)
在行範圍[firstRow, lastRow]內存在的行被刪除了,包含 firstRow 和 lastRow
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);
    }

    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 是顏色的固定邊緣
                //table.getSelectionBackground().
                setBorder(selectedBorder);
            } else {
                ...
                //unselectedBorder 是顏色的固定邊緣
                //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);

雖然上個例子裏的工具提示文本樣例是靜態的,可是你也能夠實現隨着單元格狀態改變而改變的工具提示文本程序,這裏有兩種實現的方法:
一、在渲染器的 getTableCellRendererComponent() 方法實現中增長壹些代碼;
二、重載 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 的實例而且指定它是你的表格中的排序對象。

TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(sorter);
TableRowSorter 使用 java.util.Comparator 對象來完成行排序。壹個實現了這樣接口的類提供壹個 compare() 方法用於定義任意兩個對象爲了完成排序應該如何進行比較。舉例來講,下述的代碼建立了壹個比較器,用於對比壹系列的字符串經過對比每個字符串最後壹個字母來進行排序:
Comparator<String> comparator = new Comparator<String>() {
    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]);
    }
};
這個例子是特地簡化過的,更有表明性的,壹個比較器的實現是壹個 java.text.Collator 的子類。你能夠定義你本身的子類,使用在 Collator 類中的工廠方法來根據指定的語言得到壹個 Comparator 對象,或者使用 java.text.RuleBasedCollator 類。

這個例子是特意簡化過的;更常見狀況下,Comparator 的實現是 java.text.Collator 的壹個子類。你能夠有你本身的子類,使用在 Collator 類的工廠方法能夠獲取壹個基於本地環境的 Comparator 實例,或者使用 java.text.RuleBasedCollator

對於每壹列應該使用哪一種 Comparator ,TableRowSorter 會嘗試應用以下規則。規則會按照以下的順序被遵循;第壹條規則若是已經使用,則後繼的規則會被忽略。

一、若是已經經過 setComparator() 指定了壹個比較器,則使用該比較器。
二、若是 table model 已經聲明列數據由字符串組成,就是說對於指定列調用 TableModel.getColumnClass() 方法返回 String.class,使用基於本地環境的字符串順序進行排序。
三、若是針對列調用 TableModel.getColumnClass() 以後的返回值實現了 Comparable 接口,則使用 Comparable.compareTo() 方法的返回值的字符串順序進行排序。
四、若是表格調用了 setStringConverter() 方法指定了字符串轉換器,則使用基於本地環境的字符串的表現形式的字符串排序器排序。
五、若是以上規則均不適用,則先調用列中數據的 toString() 方法將其轉換成字符串類型,而後針對轉換後的字符串使用基於本地環境的字符串順序進行排序。

對於更多更加複雜的排序類型,能夠繼承 TableRowSorteror 的父類 javax.swing.DefaultRowSorter

調用 setSortKeys() 能夠指定列的排序順序和排序優先級。這裏有壹個針對表格的前兩列進行排序的例子。優先的列能夠在排序鍵的列表中指定。在這種狀況下,第二列擁有第壹個排序鍵,因此這些行先按照 first name 排序,而後再按照 second name 排序。

List <RowSorter.SortKey> sortKeys  = new ArrayList<RowSorter.SortKey>();
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<MyTableModel>(model);
table = new JTable(model);
table.setRowSorter(sorter);

而後你能夠基於文本域的當前值過濾:

private void newFilter() {
    RowFilter<MyTableModel, Object> rf = 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 類實現這個接口並容許你指定編輯組成部分是一個JTextFieldJCheckBox 或者 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() 方法。沒有這個調用,編輯器會仍然保持活動狀態,儘管模態對話框已經再也不可見。經過調用 fireEditingStopped() 方法可讓表格知道它能夠關閉編輯器,讓單元格能夠再次被渲染器處理。

使用編輯器驗證用戶輸入的文本信息的合法性

若是一個單元格的默認編輯容許文本輸入若是單元格的類型指定爲字符串或對象之外東西,你會獲得一些免費的錯誤檢查錯誤檢查的反作用是將輸入的文字轉換成一個對象的正確類型

自動檢查用戶輸入字符串時發生在默認編輯器試圖建立一個新的單元格的列相關聯的類實例的時候默認編輯器使用一個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 ,轉載請註明原始出處。

相關文章
相關標籤/搜索