1. 程式人生 > >JavaSwing_4.8: JTable(表格)

JavaSwing_4.8: JTable(表格)

1. 概述

JTable,表格。JTable 是用來顯示和編輯常規二維單元表。

2. 建立簡單的表格

package com.xiets.swing;

import javax.swing.*;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("測試視窗");
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 建立內容面板,使用邊界佈局
JPanel panel = new JPanel(new BorderLayout()); // 表頭(列名) Object[] columnNames = {"姓名", "語文", "數學", "英語", "總分"}; // 表格所有行資料 Object[][] rowData = { {"張三", 80, 80, 80, 240}, {"John", 70, 80, 90, 240}, {"Sue", 70, 70, 70, 210}, {"Jane"
, 80, 70, 60, 210}, {"Joe", 80, 70, 60, 210} }; // 建立一個表格,指定 所有行資料 和 表頭 JTable table = new JTable(rowData, columnNames); // 把 表頭 新增到容器頂部(使用普通的中間容器新增表格時,表頭 和 內容 需要分開新增) panel.add(table.getTableHeader(), BorderLayout.NORTH); // 把 表格內容 新增到容器中心 panel.add(table, BorderLayout.CENTER); jf.setContentPane(panel); jf.pack(); jf.setLocationRelativeTo(null
); jf.setVisible(true); } }

結果展示:

result_01.png

表格元件和其他普通元件一樣,需要新增到中間容器中才能顯示,新增表格到容器中有兩種方式:

  1. 新增到普通的中間容器中(如上面程式碼例項所示的新增到JPanel),此時新增的jTable只是表格的行內容,表頭(jTable.getTableHeader())需要額外單獨新增。此新增方式適合表格行數確定,資料量較小,能一次性顯示完的表格;
  2. 新增到JScrollPane滾動容器中,此新增方式不需要額外新增表頭,jTable新增到jScrollPane中後,表頭自動新增到滾動容器的頂部,並支援行內容的滾動(滾動行內容時,表頭會始終在頂部顯示)。

3. JTable 常用的操作方法

JTable常用構造方法:

// 建立空表格,後續再新增相應資料
JTable() 

// 建立指定行列數的空表格,表頭名稱預設使用大寫字母(A, B, C ...)依次表示
JTable(int numRows, int numColumns) 

// 建立表格,指定 表格行資料 和 表頭名稱
JTable(Object[][] rowData, Object[] columnNames)

// 使用表格模型建立表格
JTable(TableModel dm)

JTable 字型網格 顏色設定:

// 設定內容字型
void setFont(Font font)

// 設定字型顏色
void setForeground(Color fg)

// 設定被選中的行前景(被選中時字型的顏色)
void setSelectionForeground(Color selectionForeground)

// 設定被選中的行背景
void setSelectionBackground(Color selectionBackground)

// 設定網格顏色
void setGridColor(Color gridColor)

// 設定是否顯示網格
void setShowGrid(boolean showGrid)

// 水平方向網格線是否顯示
void setShowHorizontalLines(boolean showHorizontalLines)

// 豎直方向網格線是否顯示
void setShowVerticalLines(boolean showVerticalLines)

JTable 表頭 設定:

// 獲取表頭
JTableHeader jTableHeader = jTable.getTableHeader();

// 設定表頭名稱字型樣式
jTableHeader.setFont(Font font);

// 設定表頭名稱字型顏色
jTableHeader.setForeground(Color fg);

// 設定使用者是否可以通過在頭間拖動來調整各列的大小。
jTableHeader.setResizingAllowed(boolean resizingAllowed);

// 設定使用者是否可以拖動列頭,以重新排序各列。
jTableHeader.setReorderingAllowed(boolean reorderingAllowed);

JTable 行列 相關設定:

// 設定所有行的行高
void setRowHeight(int rowHeight)

// 設定指定行的行高
void setRowHeight(int row, int rowHeight)

/**
 * 設定當手動改變某列列寬時,其他列的列寬自動調整模式,可選值:
 *     JTable.AUTO_RESIZE_ALL_COLUMNS 在所有的調整大小操作中,按比例調整所有的列。
 *     JTable.AUTO_RESIZE_LAST_COLUMN 在所有的調整大小操作中,只對最後一列進行調整。
 *     JTable.AUTO_RESIZE_NEXT_COLUMN 在 UI 中調整了一個列時,對其下一列進行相反方向的調整。
 *     JTable.AUTO_RESIZE_OFF 不自動調整列的寬度;使用滾動條。
 *     JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS 在 UI 調整中,更改後續列以保持總寬度不變;此為預設行為。
 */
void setAutoResizeMode(int mode)

/*
 * 調整列寬
 */
// 先獲取到某列
TableColumn tableColumn = jTable.getColumnModel().getColumn(int columnIndex);

// 設定列的寬度、首選寬度、最小寬度、最大寬度
tableColumn.setWidth(int width);
tableColumn.setPreferredWidth(int preferredWidth);
tableColumn.setMinWidth(int minWidth);
tableColumn.setMaxWidth(int maxWidth);

// 調整該列的列寬,以適合其標題單元格的寬度。
tableColumn.sizeWidthToFit();

// 是否允許手動改變該列的列寬
tableColumn.setResizable(boolean isResizable);

// 設定該列的表頭名稱
tableColumn.setHeaderValue(Object headerValue);

JTable 資料 相關操作:

/*
 * 表格資料的簡單設定和獲取
 */
// 設定表格中指定單元格的資料
jTable.getModel().setValueAt(Object aValue, int rowIndex, int columnIndex);

// 獲取表格中指定單元格的資料
Object value = jTable.getModel().getValueAt(int rowIndex, int columnIndex);

4. 建立帶滾動條的表格

建立帶滾動條的表格基本步驟:

// 建立表格
JTable table = new JTable(...);

/* 設定表格相關資料 */

// 設定滾動面板視口大小(超過該大小的行資料,需要拖動滾動條才能看到)
table.setPreferredScrollableViewportSize(new Dimension(int width, int height));

// 建立滾動面板,把 表格 放到 滾動面板 中(表頭將自動新增到滾動面板頂部)
JScrollPane scrollPane = new JScrollPane(table);

/* 再把滾動面板 scrollPane 新增到其他容器中顯示 */

完整例項程式碼:

package com.xiets.swing;

import javax.swing.*;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("測試視窗");
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 建立內容面板
        JPanel panel = new JPanel();

        // 表頭(列名)
        String[] columnNames = {"序號", "姓名", "語文", "數學", "英語", "總分"};

        // 表格所有行資料
        Object[][] rowData = {
                {1, "張三", 80, 80, 80, 240},
                {2, "John", 70, 80, 90, 240},
                {3, "Sue", 70, 70, 70, 210},
                {4, "Jane", 80, 70, 60, 210},
                {5, "Joe_05", 80, 70, 60, 210},
                {6, "Joe_06", 80, 70, 60, 210},
                {7, "Joe_07", 80, 70, 60, 210},
                {8, "Joe_08", 80, 70, 60, 210},
                {9, "Joe_09", 80, 70, 60, 210},
                {10, "Joe_10", 80, 70, 60, 210},
                {11, "Joe_11", 80, 70, 60, 210},
                {12, "Joe_12", 80, 70, 60, 210},
                {13, "Joe_13", 80, 70, 60, 210},
                {14, "Joe_14", 80, 70, 60, 210},
                {15, "Joe_15", 80, 70, 60, 210},
                {16, "Joe_16", 80, 70, 60, 210},
                {17, "Joe_17", 80, 70, 60, 210},
                {18, "Joe_18", 80, 70, 60, 210},
                {19, "Joe_19", 80, 70, 60, 210},
                {20, "Joe_20", 80, 70, 60, 210}
        };

        // 建立一個表格,指定 表頭 和 所有行資料
        JTable table = new JTable(rowData, columnNames);

        // 設定表格內容顏色
        table.setForeground(Color.BLACK);                   // 字型顏色
        table.setFont(new Font(null, Font.PLAIN, 14));      // 字型樣式
        table.setSelectionForeground(Color.DARK_GRAY);      // 選中後字型顏色
        table.setSelectionBackground(Color.LIGHT_GRAY);     // 選中後字型背景
        table.setGridColor(Color.GRAY);                     // 網格顏色

        // 設定表頭
        table.getTableHeader().setFont(new Font(null, Font.BOLD, 14));  // 設定表頭名稱字型樣式
        table.getTableHeader().setForeground(Color.RED);                // 設定表頭名稱字型顏色
        table.getTableHeader().setResizingAllowed(false);               // 設定不允許手動改變列寬
        table.getTableHeader().setReorderingAllowed(false);             // 設定不允許拖動重新排序各列

        // 設定行高
        table.setRowHeight(30);

        // 第一列列寬設定為40
        table.getColumnModel().getColumn(0).setPreferredWidth(40);

        // 設定滾動面板視口大小(超過該大小的行資料,需要拖動滾動條才能看到)
        table.setPreferredScrollableViewportSize(new Dimension(400, 300));

        // 把 表格 放到 滾動面板 中(表頭將自動新增到滾動面板頂部)
        JScrollPane scrollPane = new JScrollPane(table);

        // 新增 滾動面板 到 內容面板
        panel.add(scrollPane);

        // 設定 內容面板 到 視窗
        jf.setContentPane(panel);

        jf.pack();
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }

}

結果展示:

result_02.png

5. 表格模型(TableModel)

TableModel 介面指定了 JTable 用於詢問表格式資料模型的方法。TableModel 封裝了表格中的各種資料,為表格顯示提供資料。上面案例中直接使用行資料和表頭建立表格,實際上JTable 內部自動將傳入的行資料和表頭封裝成了 TableModel。

只要資料模型實現了 TableModel 介面,就可以通過以下兩行程式碼設定 JTable 顯示該模型:

TableModel myData = new MyTableModel(); 
JTable table = new JTable(myData);

TableModel 介面中的方法:

package javax.swing.table;

import javax.swing.*;
import javax.swing.event.*;

public interface TableModel {
    /** 返回總行數 */
    public int getRowCount();

    /** 返回總列數 */
    public int getColumnCount();

    /** 返回指定列的名稱(表頭名稱) */
    public String getColumnName(int columnIndex);

    /** 針對列中所有的單元格值,返回最具體的超類。JTable 使用此方法來設定列的預設渲染器和編輯器。 */
    public Class<?> getColumnClass(int columnIndex);

    /** 判斷指定單元格是否可編輯 */
    public boolean isCellEditable(int rowIndex, int columnIndex);

    /** 獲取指定單元格的值 */
    public Object getValueAt(int rowIndex, int columnIndex);

    /** 設定指定單元格的值 */
    public void setValueAt(Object aValue, int rowIndex, int columnIndex);

    /** 新增表格模型監聽器 */
    public void addTableModelListener(TableModelListener l);

    /** 移除表格模型監聽器 */
    public void removeTableModelListener(TableModelListener l);
}

JRE 中常用的已實現 TableModel 介面的類有兩個:

此抽象類為 TableModel 介面中的大多數方法提供預設實現。它負責管理偵聽器,併為生成 TableModelEvents 以及將其排程到偵聽器提供方便。要建立一個具體的 TableModel 作為 AbstractTableModel 的子類,只需提供對以下三個方法的實現:

public int getRowCount();
public int getColumnCount();
public Object getValueAt(int row, int column);

這是 TableModel 的一個實現,它使用一個 Vector 來儲存單元格的值物件,該 Vector 由多個 Vector 組成。DefaultTableModel 還增加了許多方便操作表格資料的方法,例如 支援 新增 和 刪除 行列 等操作

下面使用 AbstractTableModel 建立一個表格:

package com.xiets.swing;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("測試視窗");
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 建立內容面板,使用邊界佈局
        JPanel panel = new JPanel(new BorderLayout());

        // 使用表格模型建立一個表格
        JTable table = new JTable(new MyTableModel());

        // 把 表頭 新增到容器頂部(使用普通的中間容器新增表格時,表頭 和 內容 需要分開新增)
        panel.add(table.getTableHeader(), BorderLayout.NORTH);
        // 把 表格內容 新增到容器中心
        panel.add(table, BorderLayout.CENTER);

        jf.setContentPane(panel);
        jf.pack();
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }

    /**
     * 表格模型實現,表格顯示資料時將呼叫模型中的相應方法獲取資料進行表格內容的顯示
     */
    public static class MyTableModel extends AbstractTableModel {
        /**
         * 表頭(列名)
         */
        private Object[] columnNames = {"姓名", "語文", "數學", "英語", "總分"};

        /**
         * 表格所有行資料
         */
        private Object[][] rowData = {
                {"張三", 80, 80, 80, 240},
                {"John", 70, 80, 90, 240},
                {"Sue", 70, 70, 70, 210},
                {"Jane", 80, 70, 60, 210},
                {"Joe", 80, 70, 60, 210}
        };

        /**
         * 返回總行數
         */
        @Override
        public int getRowCount() {
            return rowData.length;
        }

        /**
         * 返回總列數
         */
        @Override
        public int getColumnCount() {
            return columnNames.length;
        }

        /**
         * 返回列名稱(表頭名稱),AbstractTableModel 中對該方法的實現預設是以
         * 大寫字母 A 開始作為列名顯示,所以這裡需要重寫該方法返回我們需要的列名。
         */
        @Override
        public String getColumnName(int column) {
            return columnNames[column].toString();
        }

        /**
         * 返回指定單元格的顯示的值
         */
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return rowData[rowIndex][columnIndex];
        }
    }

}

結果展示:

result_01.png

用滑鼠點選相應的單元格,會發現單元格不可編輯,因為 AbstractTableModel 中對 isCellEditable(…) 方法的實現是返回 false,即單元格不可編輯。如果某些單元格需要支援編輯,可以重寫 isCellEditable(…) 方法針對相應的單元格返回 true 即可。

6. 表格資料改變的監聽(TableModelListener)

表格的資料維護,對資料的監聽,都是由 表格模型(TableModel)來維護,通過設定表格模型監聽器(TableModelListener),可以監聽表格單元格資料的更改,表格行列的增加和移除。

設定表格模型監聽器主要程式碼:

// 先獲取 表格模型 物件
TableModel tableModel = table.getModel();

// 在 表格模型上 新增 資料改變監聽器
tableModel.addTableModelListener(new TableModelListener() {
    @Override
    public void tableChanged(TableModelEvent e) {
        // 第一個 和 最後一個 被改變的行(只改變了一行,則兩者相同)
        int firstRow = e.getFirstRow();
        int lastRow = e.getLastRow();

        // 被改變的列
        int column = e.getColumn();

        // 事件的型別,可能的值有:
        //     TableModelEvent.INSERT   新行或新列的新增
        //     TableModelEvent.UPDATE   現有資料的更改
        //     TableModelEvent.DELETE   有行或列被移除
        int type = e.getType();
    }
});

程式碼例項:

package com.xiets.swing;

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("測試視窗");
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 建立內容面板,使用邊界佈局
        JPanel panel = new JPanel(new BorderLayout());

        // 表頭(列名)
        final Object[] columnNames = {"姓名", "語文", "數學", "英語", "總分"};

        // 表格所有行資料
        final Object[][] rowData = {
                {"張三", 80, 80, 80, 240},
                {"John", 70, 80, 90, 240},
                {"Sue", 70, 70, 70, 210},
                {"Jane", 80, 70, 60, 210},
                {"Joe", 80, 70, 60, 210}
        };

        // 自定義表格模型,建立一個表格
        JTable table = new JTable(new AbstractTableModel() {
            @Override
            public int getRowCount() {
                return rowData.length;
            }

            @Override
            public int getColumnCount() {
                return rowData[0].length;
            }

            @Override
            public String getColumnName(int column) {
                return columnNames[column].toString();
            }

            @Override
            public boolean isCellEditable(int rowIndex, int columnIndex) {
                // 總分列的索引為 4,總分列不允許編輯,其他列允許編輯,
                // 總分列的數值由 語文、數學、英語 這三列的值相加得出,並同步更新
                return columnIndex != 4;
            }

            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                return rowData[rowIndex][columnIndex];
            }

            @Override
            public void setValueAt(Object newValue, int rowIndex, int columnIndex) {
                // 設定新的單元格資料時,必須把新值設定到原資料數值中,
                // 待更新UI重新呼叫 getValueAt(...) 獲取單元格值時才能獲取到最新值
                rowData[rowIndex][columnIndex] = newValue;
                // 設定完資料後,必須通知表格去更新UI(重繪單元格),否則顯示的資料不會改變
                fireTableCellUpdated(rowIndex, columnIndex);
            }
        });

        /*
         * 上面的繼承 AbstractTableModel 實現自定義表格模型,功能並不完整,還有很多需要自己
         * 去實現(例如更新資料,通知UI更新,列名稱獲取等),建議使用 DefaultTableModel 類,
         * 該類對 TableModel 做了較為完善的實現,支援自動更新資料處理,支援UI自動更新,列名稱
         * 處理,新增/移除行列等。無特殊要求不需要重寫方法,直接使用即可,如下兩行程式碼即可:
         */
        // DefaultTableModel tableModel = new DefaultTableModel(rowData, columnNames);
        // JTable table = new JTable(tableModel);

        // 獲取 表格模型
        final TableModel tableModel = table.getModel();
        // 在 表格模型上 新增 資料改變監聽器
        tableModel.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                // 獲取 第一個 和 最後一個 被改變的行(只改變了一行,則兩者相同)
                int firstRow = e.getFirstRow();
                int lastRow = e.getLastRow();

                // 獲取被改變的列
                int column = e.getColumn();

                // 事件的型別,可能的值有:
                //     TableModelEvent.INSERT   新行或新列的新增
                //     TableModelEvent.UPDATE   現有資料的更改
                //     TableModelEvent.DELETE   有行或列被移除
                int type = e.getType();

                // 針對 現有資料的更改 更新其他單元格資料
                if (type == TableModelEvent.UPDATE) {
                    // 只處理 語文、數學、英語 這三列(索引分別為1、2、3)的分數的更改
                    if (column < 1 || column > 3) {
                        return;
                    }
                    // 遍歷每一個修改的行,單個學科分數更改後同時更新總分數
                    for (int row = firstRow; row <= lastRow; row++) {
                        // 獲取當前行的 語文、數學、英語 的值
                        Object chineseObj = tableModel.getValueAt(row, 1);
                        Object mathObj = tableModel.getValueAt(row, 2);
                        Object englishObj = tableModel.getValueAt(row, 3);

                        // 把物件值轉換為數值
                        int chinese = 0;
                        try {
                            chinese = Integer.parseInt("" + chineseObj);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                        int math = 0;
                        try {
                            math = Integer.parseInt("" + mathObj);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                        int english = 0;
                        try {
                            english = Integer.parseInt("" + englishObj);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }

                        // 重新計算新的總分數
                        int totalScore = chinese + math + english;
                        // 將新的分數值設定到總分單元格(總分數的列索引為4)
                        tableModel.setValueAt(totalScore, row, 4);
                    }
                }
            }
        });

        // 把 表頭 新增到容器頂部(使用普通的中間容器新增表格時,表頭 和 內容 需要分開新增)
        panel.add(table.getTableHeader(), BorderLayout.NORTH);
        // 把 表格內容 新增到容器中心
        panel.add(table, BorderLayout.CENTER);

        jf.setContentPane(panel);
        jf.pack();
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }

}

結果展示,修改單科分數,按回車,看總分變化:

result_03.gif

7. 表格選擇器(ListSelectionModel)

表格資料的選擇使用 ListSelectionModel 選擇器模型維護,允許使用者以不同的模式選中表格中的資料。

ListSelectionModel 的使用:

// 建立表格
final JTable table = new JTable(...);

// 設定是否允許單元格單個選中,預設為 false
table.setCellSelectionEnabled(boolean cellSelectionEnabled);

// 首先通過表格物件 table 獲取選擇器模型
ListSelectionModel selectionModel = table.getSelectionModel();

// 設定選擇器模式,引數可能的值為:
//     ListSelectionModel.MULTIPLE_INTERVAL_SELECTION   一次選擇一個或多個連續的索引範圍(預設)
//     ListSelectionModel.SINGLE_INTERVAL_SELECTION     一次選擇一個連續的索引範圍
//     ListSelectionModel.SINGLE_SELECTION              一次只能選擇一個列表索引
selectionModel.setSelectionMode(int selectionMode);

// 新增選擇模型監聽器(選中狀態改變時回撥)
selectionModel.addListSelectionListener(new ListSelectionListener() {
    @Override
    public void valueChanged(ListSelectionEvent e) {
        // 獲取選中的第一行
        int selectedRow = table.getSelectedRow();

        // 獲取選中的第一列
        int selectedRow = table.getSelectedColumn();        

        // 獲取選中的所有行
        int[] selectedRows = table.getSelectedRows();

        // 獲取選中的所有列
        int[] selectedColumns = table.getSelectedColumns();
    }
});

8. 單元格的渲染器(TableCellRenderer)

單元格渲染器用於指定每一個單元格的顯示樣式。

下面程式碼例項實現表格的 偶數行背景設定為白色,奇數行背景設定為灰色,第一列的內容水平居中對齊,最後一列的內容水平右對齊,其他列的內容水平左對齊。

package com.xiets.swing;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("測試視窗");
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 建立內容面板,使用邊界佈局
        JPanel panel = new JPanel(new BorderLayout());

        // 表頭(列名)
        Object[] columnNames = {"姓名", "語文", "數學", "英語", "總分"};

        // 表格所有行資料
        Object[][] rowData = {
                {"張三", 80, 80, 80, 240},
                {"John", 70, 80, 90, 240},
                {"Sue", 70, 70, 70, 210},
                {"Jane", 80, 70, 60, 210},
                {"Joe_01", 80, 70, 60, 210},
                {"Joe_02", 80, 70, 60, 210},
                {"Joe_03", 80, 70, 60, 210},
                {"Joe_04", 80, 70, 60, 210},
                {"Joe_05", 80, 70, 60, 210}
        };

        // 建立一個表格,指定 所有行資料 和 表頭
        JTable table = new JTable(rowData, columnNames);

        // 建立單元格渲染器
        MyTableCellRenderer renderer = new MyTableCellRenderer();

        // 遍歷表格的每一列,分別給每一列設定單元格渲染器
        for (int i = 0; i < columnNames.length; i++) {
            // 根據 列名 獲取 表格列
            TableColumn tableColumn = table.getColumn(columnNames[i]);
            // 設定 表格列 的 單元格渲染器
            tableColumn.setCellRenderer(renderer);
        }

        // 如果需要自定義表頭樣式,也可以給表頭設定一個自定義渲染器
        // table.getTableHeader().setDefaultRenderer(headerRenderer);

        // 把 表頭 新增到容器頂部(使用普通的中間容器新增表格時,表頭 和 內容 需要分開新增)
        panel.add(table.getTableHeader(), BorderLayout.NORTH);
        // 把 表格內容 新增到容器中心
        panel.add(table, BorderLayout.CENTER);

        jf.setContentPane(panel);
        jf.pack();
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }

    /**
     * 單元格渲染器,繼承已實現渲染器介面的預設渲染器 DefaultTableCellRenderer
     */
    public static class MyTableCellRenderer extends DefaultTableCellRenderer {
        /**
         * 返回預設的表單元格渲染器,此方法在父類中已實現,直接呼叫父類方法返回,在返回前做相關引數的設定即可
         */
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            // 偶數行背景設定為白色,奇數行背景設定為灰色
            if (row % 2 == 0) {
                setBackground(Color.WHITE);
            } else {
                setBackground(Color.LIGHT_GRAY);
            }

            // 第一列的內容水平居中對齊,最後一列的內容水平右對齊,其他列的內容水平左對齊
            if (column == 0) {
                setHorizontalAlignment(SwingConstants.CENTER);
            } else if (column == (table.getColumnCount() - 1)) {
                setHorizontalAlignment(SwingConstants.RIGHT);
            } else {
                setHorizontalAlignment(SwingConstants.LEFT);
            }

            // 設定提示文字,當滑鼠移動到當前(row, column)所在單元格時顯示的提示文字
            setToolTipText("提示的內容: " + row + ", " + column);

            // PS: 多個單元格使用同一渲染器時,需要自定義的屬性,必須每次都設定,否則將自動沿用上一次的設定。

            /*
             * 單元格渲染器為表格單元格提供具體的顯示,實現了單元格渲染器的 DefaultTableCellRenderer 繼承自
             * 一個標準的元件類 JLabel,因此 JLabel 中相應的 API 在該渲染器實現類中都可以使用。
             *
             * super.getTableCellRendererComponent(...) 返回的實際上是當前物件(this),即 JLabel 例項,
             * 也就是以 JLabel 的形式顯示單元格。
             *
             * 如果需要自定義單元格的顯示形式(比如顯示成按鈕、複選框、內嵌表格等),可以在此自己建立一個標準組件
             * 例項返回。
             */

            // 呼叫父類的該方法完成渲染器的其他設定
            return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

}

結果展示:

result_04.png

9. 單元格資料的編輯器(TableCellEditor)

渲染器是用於正常顯示單元格資料時提供顯示元件,編輯器則是用於編輯單元格資料時顯示(使用)的元件。

使用編輯器,可控制單元格內輸入的內容格式,監聽輸入的內容變化等。

常用的編輯元件為 JTextField(文字框),也可使用 JCheckBox(複選框)、JComboBox(文字框) 等元件作為編輯元件。

編輯器介面為TableCellEditor,只有一個方法,即為指定的單元格提供一個編輯元件。實際使用時通常使用已實現了該介面的預設編輯器DefaultCellEditor

下面程式碼使用 DefaultCellEditor 作為基類自定義一個只能輸入數字的編輯器:

package com.xiets.swing;

import javax.swing.*;
import javax.swing.table.TableColumn;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("測試視窗");
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        // 建立內容面板,使用邊界佈局
        JPanel panel = new JPanel(new BorderLayout());

        // 表頭(列名)
        Object[] columnNames = {"姓名", "語文", "數學", "英語", "總分"};

        // 表格所有行資料
        Object[][] rowData = {
                {"張三", 80, 80, 80, 240},
                {"John", 70, 80, 90, 240},
                {"Sue", 70, 70, 70, 210},
                {"Jane", 80, 70, 60, 210},
                {"Joe", 80, 70, 60, 210}
        };

        // 建立一個表格,指定 所有行資料 和 表頭
        JTable table = new JTable(rowData, columnNames);

        // 建立單元格編輯器,使用文字框作為編輯元件
        MyCellEditor cellEditor = new MyCellEditor(new JTextField());

        // 遍歷表格中所有數字列,並設定列單元格的編輯器
        for (int i = 1; i < columnNames.length; i++) {
            // 根據 列名 獲取 表格列
            TableColumn tableColumn = table.getColumn(columnNames[i]);
            // 設定 表格列 的 單元格編輯器
            tableColumn.setCellEditor(cellEditor);
        }

        // 把 表頭 新增到容器頂部(使用普通的中間容器新增表格時,表頭 和 內容 需要分開新增)
        panel.add(table.getTableHeader(), BorderLayout.NORTH);
        // 把 表格內容 新增到容器中心
        panel.add(table, BorderLayout.CENTER);

        jf.setContentPane(panel);
        jf.pack();
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }

    /**
     * 只允許輸入數字的單元格編輯器
     */
    public static class MyCellEditor extends DefaultCellEditor {

        public MyCellEditor(JTextField textField) {
            super(textField);
        }

        public MyCellEditor(JCheckBox checkBox) {
            super(checkBox);
        }

        public MyCellEditor(JComboBox comboBox) {
            super(comboBox);
        }

        @Override
        public boolean stopCellEditing() {
            // 獲取當前單元格的編輯器元件
            Component comp = getComponent();

            // 獲取當前單元格編輯器輸入的值
            Object obj = getCellEditorValue();

            // 如果當前單元格編輯器輸入的值不是數字,則返回 false(表示資料非法,不允許設定,無法儲存)
            if (obj == null || !obj.toString().matches("[0-9]*")) {
                // 資料非法時,設定編輯器元件內的內容顏色為紅色
                comp.setForeground(Color.RED);
                return false;
            }

            // 資料合法時,設定編輯器元件內的內容顏