# Java Swing 測試 : 表格 JTable
# http://docs.oracle.com/javase/7/docs/api/
此篇要繼續紀錄我的 JTable 測試, 這些測試主要根據 Oracle 的 Java Tutorial 以及下面幾本書的範例綜合改寫而成 :
- 精通 Java Swing 程式設計 (林智揚, 金禾 2001)
- Java Swing 進階篇 (歐萊里)
- JFC Swing 教學手冊第二版 (碁峰)
- JBuilder 8.0 JFC and Swing programming
- ListSelectionModel.SINGLE_SELECTION (=0)
- ListSelectionModel.SINGLE_INTERVAL_SELECTION (=1)
- ListSelectionModel.MULTIPLE_INTERVAL_SELECTION (=2, 預設)
JTable 中與選取有關的方法如下 :
- setCellSelectionEnabled() / getCellSelectionEnabled() :
設定/傳回儲存格是否為可選取狀態 (boolean), 預設為 false. - setRowSelectionAllowed() / setColumnSelectionAllowed() :
設定列 / 行 (欄) 為可選取狀態 (boolean). - getRowSelectionAllowed() / getColumnSelectionAllowed() :
設定列 / 行 (欄) 為可選取狀態 (boolean) - setSelectionMode() / getSelectionMode() :
設定/傳回表格的選取模式 (int), 預設為多重區間選取. - setSelectionModel() / getSelectionModel() :
設定 / 傳回表格的 ListSelectionModel 物件. - getSelectedRows() / getSelectedColumns() :
傳回選取之列元素 / 行元素 (欄) 索引陣列 (int[]) - getSelectedRow() / getSelectedColumn() :
傳回選取之第一個列元素 / 行元素 (欄) 索引 (int), 若未選取傳回 -1. - getSelectedRowCount() / getSelectedColumnCount() :
傳回選取之列元素 / 行元素 (欄) 個數 (int). - setSelectionBackground() / getSelectionBackground()
設定 / 傳回表格的選取背景色 (Color 物件). - setSelectionForeground() / getSelectionForeground()
設定 / 傳回表格的選取前景色 (Color 物件).
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class JTable9 implements ActionListener,ListSelectionListener {
JFrame f;
JTable jt;
ListSelectionModel lsm;
JLabel lb_mode;
JLabel lb_cell;
JLabel lb_row;
JLabel lb_column;
JLabel lb_selected;
public static void main(String argv[]) {
new JTable9();
}
public JTable9() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
MyTableModel mtm=new MyTableModel();
jt=new JTable(mtm);
jt.setPreferredScrollableViewportSize(f.getSize()); //與 JFrame 相同大小
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
//Add menu bar
JMenuBar mb=new JMenuBar();
JMenu jtable=new JMenu("JTable");
//selection mode 製作功能表
JMenu selectionMode=new JMenu("SelectionMode");
JRadioButtonMenuItem[] mode=new JRadioButtonMenuItem[3];
mode[0]=new JRadioButtonMenuItem("SINGLE_SELECTION(0)");
mode[1]=new JRadioButtonMenuItem("SINGLE_INTERVAL_SELECTION(1)");
mode[2]=new JRadioButtonMenuItem("MULTIPLE_INTERVAL_SELECTION(2)");
ButtonGroup modegroup=new ButtonGroup();
for (int i=0; i<mode.length; i++) {
selectionMode.add(mode[i]);
if (i==2) {mode[i].setSelected(true);} //預設顯示 MULTIPLE
}
jtable.add(selectionMode);
//cell selection
JMenu cellSelection=new JMenu("Cell Selection");
JRadioButtonMenuItem[] cell=new JRadioButtonMenuItem[2];
cell[0]=new JRadioButtonMenuItem("setCellSelectionEnabled(true)");
cell[1]=new JRadioButtonMenuItem("setCellSelectionEnabled(false)");
ButtonGroup cellgroup=new ButtonGroup();
for (int i=0; i<cell.length; i++) {
cellSelection.add(cell[i]);
if (i==1) {cell[i].setSelected(true);}
}
jtable.add(cellSelection);
//row selection
JMenu rowSelection=new JMenu("Row Selection");
JRadioButtonMenuItem[] row=new JRadioButtonMenuItem[2];
row[0]=new JRadioButtonMenuItem("setRowSelectionAllowed(true)");
row[1]=new JRadioButtonMenuItem("setRowSelectionAllowed(false)");
ButtonGroup rowgroup=new ButtonGroup();
for (int i=0; i<row.length; i++) {
rowSelection.add(row[i]);
if (i==1) {row[i].setSelected(true);} //顯示預設值
}
jtable.add(rowSelection);
//row selection
JMenu columnSelection=new JMenu("Column Selection");
JRadioButtonMenuItem[] column=new JRadioButtonMenuItem[2];
column[0]=new JRadioButtonMenuItem("setColumnSelectionAllowed(true)");
column[1]=new JRadioButtonMenuItem("setColumnSelectionAllowed(false)");
ButtonGroup columngroup=new ButtonGroup();
for (int i=0; i<column.length; i++) {
columnSelection.add(column[i]);
if (i==1) {column[i].setSelected(true);}
}
jtable.add(columnSelection);
mb.add(jtable);
f.setJMenuBar(mb);
//set status
lsm=jt.getSelectionModel();
lsm.addListSelectionListener(this); //選項模式物件向本物件註冊監聽
JPanel status=new JPanel(new GridLayout(1,4));
lb_mode=new JLabel("mode=" + lsm.getSelectionMode());
lb_cell=new JLabel("cell=" + jt.getCellSelectionEnabled());
lb_row=new JLabel("row=" + jt.getRowSelectionAllowed());
lb_column=new JLabel("column=" + jt.getColumnSelectionAllowed());
status.add(lb_mode);
status.add(lb_cell);
status.add(lb_row);
status.add(lb_column);
cp.add(status,BorderLayout.SOUTH);
lb_selected=new JLabel("Selected : ");
cp.add(lb_selected,BorderLayout.NORTH);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
public void actionPerformed(ActionEvent e) { //設定模式與屬性
String cmd=e.getActionCommand();
if (cmd.equals("SINGLE_SELECTION(0)")) {
lsm.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
lb_mode.setText("mode=" + lsm.getSelectionMode());
}
if (cmd.equals("SINGLE_INTERVAL_SELECTION(1)")) {
lsm.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
lb_mode.setText("mode=" + lsm.getSelectionMode());
}
if (cmd.equals("MULTIPLE_INTERVAL_SELECTION(2)")) {
lsm.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
lb_mode.setText("mode=" + lsm.getSelectionMode());
}
if (cmd.equals("setCellSelectionEnabled(true)")) {
jt.setCellSelectionEnabled(true);
lb_cell.setText("cell=true");
}
if (cmd.equals("setCellSelectionEnabled(false)")) {
jt.setCellSelectionEnabled(false);
lb_cell.setText("cell=false");
}
if (cmd.equals("setRowSelectionAllowed(true)")) {
jt.setRowSelectionAllowed(true);
lb_row.setText("row=true");
}
if (cmd.equals("setRowSelectionAllowed(false)")) {
jt.setRowSelectionAllowed(false);
lb_row.setText("row=false");
}
if (cmd.equals("setColumnSelectionAllowed(true)")) {
jt.setColumnSelectionAllowed(true);
lb_column.setText("column=true");
}
if (cmd.equals("setColumnSelectionAllowed(false)")) {
jt.setColumnSelectionAllowed(false);
lb_column.setText("column=false");
}
}
public void valueChanged(ListSelectionEvent e) { //觸發選取事件後執行
int[] rows=jt.getSelectedRows(); //取得選取列上之儲存格
int[] columns=jt.getSelectedColumns(); //取得選取欄上之儲存格
StringBuilder msg=new StringBuilder("Selected : ");
for (int i=0; i<rows.length; i++) {
for (int j=0; j<columns.length; j++) {
msg.append(jt.getValueAt(rows[i],columns[j]).toString());
}
}
lb_selected.setText(msg.toString());
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
class MyTableModel extends AbstractTableModel {
Object[][] data={
{"Kelly","Female",new Integer(16),false,"kelly@gmail.com"},
{"Peter","Male",new Integer(14),false,"peter@gmail.com"},
{"Amy","Female",new Integer(12),false,"amy@gmail.com"},
{"Tony","Male",new Integer(49),true,"tony@gmail.com"},
{"John","Male",new Integer(23),true,"john@gmail.com"},
{"Eva","Female",new Integer(19),false,"eva@gmail.com"},
{"Rebeca","Female",new Integer(9),false,"rebeca@gmail.com"}};
String[] columns={"Name","Gender","Age","Vegetarian","E-mail"};
public int getColumnCount() {return columns.length;}
public int getRowCount() {return data.length;}
public Object getValueAt(int row, int col) {return data[row][col];}
public String getColumnName(int col) {return columns[col];}
public Class getColumnClass(int col) {
return getValueAt(0,col).getClass();
}
public boolean isCellEditable(int row,int col) {return true;}
public void setValueAt(Object value,int row,int col) {
data[row][col]=value;
fireTableCellUpdated(row,col);
}
}
接下來是測試要如何在 JTable 上新增與刪除行與列? 這要用到表格預設模型 DefaultTableModel 與 TableColumn 物件. 在 DefaultTableModel 類別中定義了下列方法來操作新增行列以及刪除列 :
- addColumn(Object columnName) :
增加一欄到表格模型尾端, 但只有欄名. - addColumn(Object columnName, Object[] columnData) :
增加一欄到表格模型尾端, 包括欄名與資料. - addColumn(Object columnName, Vector columnData)
增加一欄到表格模型中, 包括欄名與資料 (使用 Vector). - addRow(Object[] rowData)
增加一列資料到表格模型的尾端. - addRow(Vector rowData)
增加一列資料到表格模型尾端 (使用 Vector). - insertRow(int row, Object[] rowData)
將資料插入指定的列. - insertRow(int row, Vector rowData)
將資料插入指定的列 (使用 Vector). - removeRow(int row):
刪除指定列資料. - moveRow(int start, int end, int to)
將連續數列資料移動到指定列.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.util.*;
public class JTable10 implements ActionListener {
JFrame f;
JTable jt;
DefaultTableModel dtm;
JLabel status;
int cid=0;
public static void main(String argv[]) {
new JTable10();
}
public JTable10() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
dtm=new DefaultTableModel();
jt=new JTable(dtm);
jt.setPreferredScrollableViewportSize(f.getSize());
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
//setup button
JPanel panel=new JPanel(new GridLayout(1,4));
JButton addColumn=new JButton("增加行");
addColumn.addActionListener(this);
JButton removeColumn=new JButton("移除行");
removeColumn.addActionListener(this);
JButton addRow=new JButton("增加列");
addRow.addActionListener(this);
JButton removeRow=new JButton("移除列");
removeRow.addActionListener(this);
panel.add(addColumn);
panel.add(removeColumn);
panel.add(addRow);
panel.add(removeRow);
cp.add(panel,BorderLayout.NORTH);
status=new JLabel(" ");
cp.add(status,BorderLayout.SOUTH);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
public void actionPerformed(ActionEvent e) {
String cmd=e.getActionCommand();
if (cmd.equals("增加行")) {
dtm.addColumn("行 " + cid);
status.setText("已新增一行, 總行數=" + dtm.getColumnCount());
++cid;
}
if (cmd.equals("增加列")) {
dtm.addRow(new Vector()); //增加空列 (Vector)
status.setText("已新增一列, 總列數=" + dtm.getRowCount());
}
if (cmd.equals("移除行")) {
int lastColumnID=dtm.getColumnCount()-1;
if (lastColumnID >= 0) {
TableColumnModel columnModel=jt.getColumnModel(); //取得表格欄模型
TableColumn column=columnModel.getColumn(lastColumnID); //取得欄物件
columnModel.removeColumn(column); //刪除欄物件
dtm.setColumnCount(lastColumnID); //更新表格模型之總欄數
status.setText("已刪除最後一行, 總行數=" + dtm.getColumnCount());
}
}
if (cmd.equals("移除列")) {
int lastRowID=dtm.getRowCount()-1;
if (lastRowID >= 0) {
dtm.removeRow(lastRowID);
dtm.setRowCount(lastRowID);
status.setText("已刪除最後一列, 總列數=" + dtm.getRowCount());
}
}
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
此例中, 新增行或列時會在最後一行或列添加新行或新列. 這裡我們用上面編號五的 addRow() 方法, 加入一個空的 Vector 物件即可, Vector 類別放在 java.util 套件下, 所以必須匯入. 注意, 當沒有任何欄位時, 新增列雖然不會顯示儲存格, 但實際上有加入到模型中, 只要新增一行即顯現.
上例中, 新增或刪除行列時, 是新增刪除最後一行或最後一列, 是否可以新增於選取行列之後, 刪除選取之行列呢? 範例程式如下 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.util.*;
public class JTable11 implements ActionListener,ListSelectionListener {
JFrame f;
JTable jt;
DefaultTableModel dtm;
JLabel status;
int cid=0;
int rid=0;
public static void main(String argv[]) {
new JTable11();
}
public JTable11() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
dtm=new DefaultTableModel();
jt=new JTable(dtm);
jt.setPreferredScrollableViewportSize(f.getSize());
jt.setCellSelectionEnabled(true);
jt.setRowSelectionAllowed(true);
jt.setColumnSelectionAllowed(true);
ListSelectionModel lsm=jt.getSelectionModel();
lsm.addListSelectionListener(this);
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
//setup button
JPanel panel=new JPanel(new GridLayout(1,4));
JButton addColumn=new JButton("增加行");
addColumn.addActionListener(this);
JButton removeColumn=new JButton("移除行");
removeColumn.addActionListener(this);
JButton addRow=new JButton("增加列");
addRow.addActionListener(this);
JButton removeRow=new JButton("移除列");
removeRow.addActionListener(this);
panel.add(addColumn);
panel.add(removeColumn);
panel.add(addRow);
panel.add(removeRow);
cp.add(panel,BorderLayout.NORTH);
status=new JLabel(" ");
cp.add(status,BorderLayout.SOUTH);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
public void actionPerformed(ActionEvent e) {
String cmd=e.getActionCommand();
if (cmd.equals("增加行")) {
int selectedColumnID=jt.getSelectedColumn();
dtm.addColumn("行 " + cid); //於尾端增加一行
int columnCount=dtm.getColumnCount(); //新總行數
if (selectedColumnID != -1) { //有選取行 : 將新增之最後行移到選取行
TableColumnModel columnModel=jt.getColumnModel();
columnModel.moveColumn(columnCount-1,selectedColumnID);
}
status.setText("已新增一行, 總行數=" + columnCount);
++cid;
}
if (cmd.equals("增加列")) {
int selectedRowID=jt.getSelectedRow();
int rowCount=dtm.getRowCount();
if (selectedRowID != -1) { //有選取 : 在選取列插入新列
dtm.insertRow(selectedRowID,new Vector());
}
else {dtm.addRow(new Vector());} //沒有選取 : 在尾端加一列
dtm.setValueAt("列 " + rid, rowCount, 0); //在第一儲存格顯示列號
status.setText("已新增一列, 總列數=" + dtm.getRowCount());
++rid;
}
if (cmd.equals("移除行")) {
int selectedColumnID=jt.getSelectedColumn();
int columnCount=dtm.getColumnCount();
int lastColumnID=columnCount-1;
if (selectedColumnID != -1) { //one column selected
TableColumnModel columnModel=jt.getColumnModel();
TableColumn column=columnModel.getColumn(selectedColumnID);
columnModel.removeColumn(column);
dtm.setColumnCount(columnCount-1);
String msg="已刪除行 id=" + selectedColumnID + ", 總行數=" +
dtm.getColumnCount();
status.setText(msg);
}
else { //no column selected
TableColumnModel columnModel=jt.getColumnModel();
TableColumn column=columnModel.getColumn(lastColumnID);
columnModel.removeColumn(column);
dtm.setColumnCount(columnCount-1);
String msg="已刪除行 id=" + lastColumnID + ", 總行數=" +
dtm.getColumnCount();
status.setText(msg);
}
}
if (cmd.equals("移除列")) {
int selectedRowID=jt.getSelectedRow();
int rowCount=dtm.getRowCount();
int lastRowID=rowCount-1;
if (selectedRowID != -1) { //有選取 : 刪除指定列
dtm.removeRow(selectedRowID);
dtm.setRowCount(rowCount-1);
String msg="已刪除列 id=" + selectedRowID + ", 總列數=" +
dtm.getRowCount();
status.setText(msg);
}
else { //無選取 : 刪除最後一列
dtm.removeRow(lastRowID);
dtm.setRowCount(rowCount-1);
String msg="已刪除列 id=" + lastRowID + ", 總列數=" +
dtm.getRowCount();
status.setText(msg);
}
}
}
public void valueChanged(ListSelectionEvent e) {
String msg="選取列 " + jt.getSelectedRow() + " 行 " +
jt.getSelectedColumn();
status.setText(msg);
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
此例中我增加了選取動作的監聽, 以便在下方的 JLabel 中顯示選取了哪一列哪一行. 在增加行或列時, 要判斷是否有選取動作, DefaultTableModel 的 getSelectedRow() 與 getSelectedColumn() 在沒有選取時會傳回 -1, 有選取時則傳回選取的 (第一個) 行或列之索引, 故可藉此來判定是要新增行列於選取之位置還是末尾. 此外, 我也在新增列時, 在第一個儲存格加入列號, 以方便與下方 JLabel 之顯示相對照.
比較特別的是, 插入列有 JTable 的 insertRow() 可用, 但插入行卻沒有 insertColumn() 可用, 變通辦法是利用 TableColumnModel 物件的 moveColumn() 方法, 先用 DefaultTableModel 的 addColumn() 新增一行於 Model 尾端 (變成新的最後一行), 再呼叫 JTable 的 getColumnModel() 取得 TableColumnModel 物件, 就可以使用其 moveColumn() 方法將此新增行從尾端移往選取行了.
刪除列可以用 DefaultTableModel 的 removeRow() 方法; 刪除行則要用 TableColumnModel 的 removeColumn() 方法. 奇怪的是, 即使傳入選取的行索引給 TableColumnModel 物件的 getColumn() 以取得 TableColumn 物件, 但刪除時卻跟無選取時一樣, 都刪除最後一行, why? 再研究.
接下來要測試 JTable 的排序功能, 這要用到 TableRowSorter 類別以及 JTable 的 setRowSorter() 方法, 其 API 如下 :
# http://docs.oracle.com/javase/7/docs/api/javax/swing/table/TableRowSorter.html
TableRowSorter 繼承自 RowSorter 與 DefaultRowSorter 類別, 要讓表格具有排序功能很簡單, 只要傳入 TableModel 物件當參數給 TableRowSorter 的建構子建立一個排序物件, 再將此排序物件傳給 JTable 的 setRowSorter() 方法即可. 注意, TableRowSorter 使用泛型宣告, 因此建立排序物件時必須使用泛型並指定 TableModel 型態, 否則會出現編譯錯誤 :
TableRowSorter
jtable.setRowSorter(sorter);
也可以先用 TableRowSorter 無參數的建構子, 再呼叫 setModel() 方法亦可 :
TableRowSorter
sorter.setModel(tablemodel);
jtable.setRowSorter(sorter);
參考 : java JTable排序和过滤
範例程式如下 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class JTable12 implements ActionListener {
JFrame f;
JTable jt;
public static void main(String argv[]) {
new JTable12();
}
public JTable12() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
MyTableModel mtm=new MyTableModel();
jt=new JTable(mtm);
TableRowSorter
jt.setRowSorter(sorter);
jt.setPreferredScrollableViewportSize(f.getSize());
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
public void actionPerformed(ActionEvent e) {
String cmd=e.getActionCommand();
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
class MyTableModel extends AbstractTableModel {
Object[][] data={
{"Kelly","Female",new Integer(16),false,"kelly@gmail.com"},
{"Peter","Male",new Integer(14),false,"peter@gmail.com"},
{"Amy","Female",new Integer(12),false,"amy@gmail.com"},
{"Tony","Male",new Integer(49),true,"tony@gmail.com"},
{"John","Male",new Integer(23),true,"john@gmail.com"},
{"Eva","Female",new Integer(19),false,"eva@gmail.com"},
{"Rebeca","Female",new Integer(9),false,"rebeca@gmail.com"}};
String[] columns={"Name","Gender","Age","Vegetarian","E-mail"};
public int getColumnCount() {return columns.length;}
public int getRowCount() {return data.length;}
public Object getValueAt(int row, int col) {return data[row][col];}
public String getColumnName(int col) {return columns[col];}
public Class getColumnClass(int col) {
return getValueAt(0,col).getClass();
}
public boolean isCellEditable(int row,int col) {return true;}
public void setValueAt(Object value,int row,int col) {
data[row][col]=value;
fireTableCellUpdated(row,col);
}
}
當點選欄位標題時, 就會以該欄之數據對列進行排序 (故取名為 RowSorter), 標題上會出現一個三角標誌, 向上為遞增排序, 向下為遞降排序.
接下來要測試此行最重要的一個測試, 即如何將資料庫中擷取出來的資料呈現於 JTable 中. 最簡單的方法是利用 DefaultTableModel 的 addRow() 方法, 此方法有兩個多載 :
- void addRow(Object[] rowData)
- void addRow(Vector rowData)
String[] columnNames={"Name","Gender","Age","Vegetarian","E-mail"};
dtm.setColumnIdentifiers(columnNames);
當然, 為了簡化資料庫存取, 我使用了之前開發的迷你 Java 套件 JT.java, 參考下列文章 :
# http://yhhuang1966.blogspot.tw/2014/04/java-access.html
範例如下 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.sql.*;
public class JTable13 {
JFrame f;
JTable jt;
public static void main(String argv[]) {
new JTable13();
}
public JTable13() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
DefaultTableModel dtm=new DefaultTableModel();
jt=new JTable(dtm);
String[] columnNames={"Name","Gender","Age","Vegetarian","E-mail"};
dtm.setColumnIdentifiers(columnNames);
TableRowSorter
jt.setRowSorter(sorter);
//connect ACCESS DB
JT.connectMDB("testdb"); //連線 ACCESS 資料庫
try {
ResultSet rs=JT.runSQL("SELECT * FROM users"); //執行 SQL 查詢
while (rs.next()) { //拜訪紀錄集的每一列
dtm.addRow(new Object[]{
rs.getObject("Name"),
rs.getObject("Gender"),
rs.getObject("Age"),
rs.getObject("Vegetarian"),
rs.getObject("E-mail")
});
}
rs.close(); //關閉紀錄集
rs=null;
}
catch (Exception e) {System.out.println(e);}
JT.closeDB(); //關閉資料庫連線
jt.setPreferredScrollableViewportSize(f.getSize());
jt.setCellSelectionEnabled(true);
jt.setRowSelectionAllowed(true);
jt.setColumnSelectionAllowed(true);
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
執行此程式前, 必須先建立一個資料庫 testdb.mdb, 裡面有一個資料表 users, 內容如下 :
除了 Vegetarian 為 YES/NO 型態, Age 為 INT 型態外, 其餘均為 VARCHAR 型態. 一般我們讀取 ResultSet 物件裡的資料時, 通常呼叫其 getString() 方法, 對於 YES/NO 型態的欄位會傳回 0/1 而非 true/false. 如果要顯示 true/false 就要用 Boolean 包裹物件的 valueOf() 方法轉換 :
Boolean.valueOf(rs.getString("Vegetarian"))
這倒不如直接使用 getObject() 來得方便.
參考資料 :
# http://tips4java.wordpress.com/2009/03/12/table-from-database/
# http://www.roseindia.net/java/example/java/swing/jtable-display-database-data.shtml
# Simple show database data in JTable
除了上面使用 DefautTableModel 的 addRow() + setColumnIdentifiers() 兩個方法來加入 JTable 內容外, 還可以使用 JTable 的最後一個建構子, 以 Vector 類別來製作 JTable 的欄位標題以及每一列內容 :
JTable(Vector rowData, Vector columnNames)
Vector 類別用來存放各類物件, 與陣列一樣用整數索引來存取元素, 其元素長度可擴展與縮減 (可變), 不像陣列是型態一致與長度固定不可變. 其常用方法如下 :
- add(E e) : 加入元素
- add(int index, E element) : 於指定索引加入元素
- addElement(E obj) : 加入元素
- contains(Object o) : 是否含有指定元素
- elementAt(int index) : 傳回指定索引的元素
- indexOf(Object o) : 傳回指定元素的索引 (無傳回 -1)
- remove(int index) : 移除指定索引元素
- remove(Object o) : 移除指定元素
- removeElementAt(int index) : 移除指定索引元素
- size() : 傳回元素個數
- toArray() : 轉成陣列傳回
這裡我們只會用到其 add() 方法來添加元素. Vector 放在 java.util 套件, 故須匯入此類別庫. 詳細 API 參見 :
# http://docs.oracle.com/javase/7/docs/api/java/util/Vector.html
程式碼如下 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.sql.*;
import java.util.*;
public class JTable14 {
JFrame f;
JTable jt;
public static void main(String argv[]) {
new JTable14();
}
public JTable14() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
Vector<String> columnNames=new Vector<String>(); //儲存欄位標題
columnNames.add("Name");
columnNames.add("Gender");
columnNames.add("Age");
columnNames.add("Vegetarian");
columnNames.add("E-mail");
//connect ACCESS DB
JT.connectMDB("testdb");
Vector<Vector<Object>> data=new Vector<Vector<Object>>(); //store a row
try {
ResultSet rs=JT.runSQL("SELECT * FROM users");
while (rs.next()) {
Vector<Object> row=new Vector<Object>(); //store each cell of a row
row.add(rs.getObject("Name")); //把欄位加入 row 的 Vector 中
row.add(rs.getObject("Gender"));
row.add(rs.getObject("Age"));
row.add(rs.getObject("Vegetarian"));
row.add(rs.getObject("E-mail"));
data.add(row);
}
rs.close();
rs=null;
}
catch (Exception e) {System.out.println(e);}
JT.closeDB();
DefaultTableModel dtm=new DefaultTableModel(data, columnNames);
jt=new JTable(dtm);
dtm.setColumnIdentifiers(columnNames);
TableRowSorter<TableModel> sorter=new TableRowSorter<TableModel>(dtm);
jt.setRowSorter(sorter);
jt.setPreferredScrollableViewportSize(f.getSize());
jt.setCellSelectionEnabled(true);
jt.setRowSelectionAllowed(true);
jt.setColumnSelectionAllowed(true);
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
執行結果與上例一樣, 故不抓圖. 這裡使用了兩個 Vector 來分別儲存欄位標題 columnNames 與表格內容 data (列資料). 注意, Vector 必須使用泛型宣告指定元素類型, 其中欄位標題只要字串即可, 而列則為二維物件資料, 因此使用兩層泛型, 型態為 Object.
參考 :
# http://stackoverflow.com/questions/18921211/transferring-data-from-database-to-jtable
上面從資料庫擷取資料表, 並將內容描繪到 JTable 的兩種方法都是使用 DefaultTableModel, 即使使用 getObject() 擷取, 描繪時都會轉成字串 (例如 YES/NO 欄位變成 true/false, 用 getString() 則變成 0/1), 無法以物件在 Swing 的對應型態描繪 (例如 YES/NO 以 checkbox 呈現). 另外預設表格模型的儲存格也是編輯無效的 (可編輯, 但無效). 如果要讓表格模型客製化, 應該繼承 AbstractTableModel 或 DefaultTableModel, 並覆寫想要客製化的方法才行.
在下面範例中, 我繼承了 AbstractTableModel 類別, 並改用 ArrayList 來儲存列資料 (當然用 Vector 也是可以的), 程式碼如下 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.sql.*;
import java.util.*;
public class JTable15 {
JFrame f;
JTable jt;
public static void main(String argv[]) {
new JTable15();
}
public JTable15() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JTable Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
//cp.setLayout(null);
//Build Elements
MyTableModel mtm=new MyTableModel();
jt=new JTable(mtm);
TableRowSorter<TableModel> sorter=new TableRowSorter<TableModel>(mtm);
jt.setRowSorter(sorter);
jt.setPreferredScrollableViewportSize(f.getSize());
jt.setCellSelectionEnabled(true);
jt.setRowSelectionAllowed(true);
jt.setColumnSelectionAllowed(true);
cp.add(new JScrollPane(jt),BorderLayout.CENTER);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowHandler(f));
}
}
class WindowHandler extends WindowAdapter {
JFrame f;
public WindowHandler(JFrame f) {this.f=f;}
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
}
class MyTableModel extends AbstractTableModel {
String[] columns={"Name","Gender","Age","Vegetarian","E-mail"};
ArrayList<ArrayList<Object>> data=new ArrayList<ArrayList<Object>>();
public MyTableModel() { //在建構子中描繪各列資料
//connect ACCESS DB
JT.connectMDB("testdb");
try {
ResultSet rs=JT.runSQL("SELECT * FROM users");
while (rs.next()) {
ArrayList<Object> row=new ArrayList<Object>(); //儲存每列之各欄資料
row.add(rs.getObject("Name"));
row.add(rs.getObject("Gender"));
row.add(rs.getObject("Age"));
row.add(rs.getObject("Vegetarian"));
row.add(rs.getObject("E-mail"));
data.add(row);
}
rs.close();
rs=null;
}
catch (Exception e) {System.out.println(e);}
JT.closeDB();
}
public int getColumnCount() {return columns.length;}
public int getRowCount() {return data.size();} //列數=ArrayList 的大小
public Object getValueAt(int row, int col) {
ArrayList<Object> rowdata=data.get(row); //取得列物件
return rowdata.get(col); //傳回指定欄位之儲存格內容
}
public String getColumnName(int col) {return columns[col];}
public Class getColumnClass(int col) {
return getValueAt(0,col).getClass();
}
public boolean isCellEditable(int row,int col) {return true;} //可編輯
public void setValueAt(Object value,int row,int col) { //使編輯有效之處理
ArrayList<Object> rowdata=data.get(row); //取得列物件
rowdata.set(col,value); //將編輯後資料更新列資料物件之指定欄
fireTableCellUpdated(row,col); //更新表格顯示
}
}
此例中讓儲存格編輯有效的關鍵是我們覆寫了 isCellEditable() 與 setValueAt() 這兩個方法, 前者讓儲存格可編輯, 後者則是讓編輯結果生效, 也就是更新了表格的資料來源 (ArrayList 中的元素). 先呼叫 ArrayList 的 get() 方法以所編輯之列索引取得列物件, 再用 set() 方法更新此列物件在所編輯欄之值, 最後呼叫 AbstractTableModel 的 fireTableCellUpdated() 方法觸發 JTable 重新描繪該儲存格, 這樣便達到有效編輯的目的. 不過這只是更改描繪結果, 與資料庫無關, users 資料表並未被更改.
參考 :
# http://www.users.csbsju.edu/~lziegler/CS317/NetProgramming/JTable.html
3 則留言 :
不好意思請問一下 如果我的程式寫成
tabbedPane.addTab("Tab ", new JTable(new DefaultTableModel()));
那我該怎麼addRow進model裡呢??
我好久沒寫 Java 囉, 您可以先參考這篇看看 :
http://yhhuang1966.blogspot.tw/2014/05/java-swing-jtabbedpane.html
TO 李穎琪:
您可以把程式改成這樣
DefaultTableModel dtm = new DefaultTableModel();
JTable jt = new JTable(dtm);
tabbedPane.addTab("Tab ", jt);
這樣一來就可以利用前兩個物件來操作囉~
張貼留言