建立一個JTable控制項很簡單,直接使用他的構造方法,如建立一個顯示二維數組資料的表格,且可以顯示列的名稱:JTable(Object[][] rowData,Object[] columnNames)
這裡rowData的二維數組是指表格的行內容,columnNames的一維數組是指表格的標題,這樣就可以建立個簡單的表格。這樣建立的表格是固定的,而要實現行的刪除、增加與插入那麼我們就要用到TableModel介面來建立表格。另外java還提供了兩個類:一個是AbstractTableModel抽象類別,一個是DefaultTableModel實體類.前者實現了大部份的TableModel方法,讓使用者可以很有彈性地構造自己的表格模式;後者繼承前者類,是java預設的表格模式。其中DefaultTableModel實現了getColumnCount(),getRowCount()與getValueAt()3個方法.因此在實際的使用上,DefaultTableModel比AbstractTableModel要來得簡單許多,也較常被拿來使用。
我們這裡就使用DefaultTableModel來構造表格模型。下面是DefaultTableModel的建構函式:
//建立一個DefaultTableModel,裡面沒有任何資料.
DefaultTableModel()
//建立一個指定行列數的DefaultTableModel.
DefaultTableModel(int numRows,int numColumns)
//建立一個DefaultTableModel,輸入資料格式為Object Array.系統會自動
//調用setDataVector()方法來設定資料。
DefaultTableModel(Object[][] data,Object[] columnNames)
//建立一個DefaultTableModel,並具有Column Header名稱與行數資訊。
DefaultTableModel(Object[] columnNames,int numRows)
//建立一個DefaultTableModel,並具有column Header名稱與行數資訊。
DefaultTableModel(Vector columnNames,int numRows)
//建立一個DefaultTableModel,輸入資料格式為Vector.系統會自動調用
//setDataVector()方法來設定資料。
DefaultTableModel(Vector data,Vector columnNames):
DefaultTableModel類提供相當多好用的方法,getColumnCount(),getRowCount(),getValueAt
(),isCellEditable(),setValueAt()等方法,均可直接使用。且DefaultTableModel也提供了addColumn()
與addRow()等方法,可讓我們隨時增加表格的資料。
比如我們要添加資料:
//defaultModel是DefaultTableModel對象。data是要增加資料的一個一維數組,addRow只能
//一次添加一行,我們每次只可能增加一條資料,所以在這裡有這條語句就夠了。
defaultModel.addRow(data);
關於修改,稍微要麻煩一點,表格是不提供一次性修改一行所有欄位的,這裡我們就可以使用兩種方法來
實現。第一、把當前表格中的所有資料儲存到一個二維數組裡面,然後在修改資料的類對要修改的資料進
行替換,再把這個數組返回給主視窗,使用setDataVector(Object[][] dataVector, Object[]
columnIdentifiers),將該數組重新寫入到表格中。我使用的是第二種方法,首先使用
table.getRowSelected()方法獲得被選中的行序號,這個序號是相對錶格上面的。然後使用
table.getValue(i,0),獲得這一行第一列中的值,第一列就是我們資料中的序號。將這個值傳遞到資料
修改視窗,當修改完成返回修改後的一維數組後:
//首先刪除原來表格中這一行的資料
defaultModel.removeRow(i);
//將新的資料插入到剛才選中那一行的序號上
defaultModel.insertRow(i, data);
前一種方法看起來比後一種方便,不過我比較傾向於使用後一種,因為如果當資料量比較大的時候,比如
有10000條資料,那每次修改都需要把所有資料重新寫入到表格中,這樣速度會有影響,如果使用第二種
方法我們只需要刪除一行,增加一行就行了,這樣在速度方面會有優勢。
說到查詢,比較麻煩點的是在查詢類裡的演算法上,而要把查詢的結果顯示在表格中是非常簡單的。當查詢
的結果出來之後,將結果儲存到一個二維數組裡,這個二維數組的大小是動態指定的,也就說它的大小根
據查詢到的結果數為準。這點在查詢類的代碼中進行分析。當結果返回後,我們只需要使用
setDataVector()方法就可以將資料顯示在表格中。
刪除要稍微麻煩點,之前提到過因為表格中的資料數量是不確定的。比如我們有10條資料,不一定表格中
就顯示出這10條資料,有可能使用者查詢到其中某幾條資料,並顯示在表格中,這個時候如果刪除其中某一
條資料,那麼剩下的資料的序號也會發生相應的變化。我在這裡的做法就是把在檔案中刪除資料,和顯示
在表格中的資料刪除分離。也就是首先進入刪除類,在這個類的方法裡面先對檔案中的資料做出相應的刪
除,然後排序。接著再把當前顯示在表格中的資料進行同樣的刪除操作,同樣進行排序,返回的是後者操
作的結果。這樣就能保證我們資料的同步性,並且在表格上也能及時反映出我們做出的刪除動作。同樣用
setDataVector()方法把結果顯示在表格中即可。
下面談下關於表格的事件。按照項目需求只需要實現一個事件,即雙擊表格中某欄位,彈出填寫資料資訊
的視窗,並可進行資料的修改。這裡我又加入了一個滑鼠右鍵單擊的事件,即當滑鼠選擇表格中某幾項或
者直接在某一項上面單擊右鍵,彈出個右鍵菜單,上面有刪除和清空的選項。選擇刪除項則刪除當前被選
中的資料項目,選擇清空項則清除當前螢幕上表格中的所有資料,注意刪除和清空的區別。清空是不會刪除
檔案中的資料,只是起到個清屏的作用。
雙擊表格事件:DefaultTableModel預設表格是可以進行編輯的,那麼當我們雙擊表格中某欄位時,表格
會自動觸發修改該欄位的事件,這樣我們想加入自己的雙擊事件就會很困難,至少我還沒找到什麼辦法能
夠雙擊後又可以觸發修改欄位的事件,同時還會響應我們添加的事件。如果有哪位高人知道方法,希望不
吝賜教,小弟在此先謝過。在還沒知道更好的辦法前,我只能先讓DefaultTableModel建立的表格模型中
每個欄位都不可編輯,可以重載DefaultTableModel的isCellEditable方法,這個方法預設是返回true,
也就是可編輯的。我們將他更改為false,這樣就能達到我們想要的效果。例如:
class MyTable extends DefaultTableModel{
public MyTable(Object[][]data,Object[]head){
super(data,head);
}
public boolean isCellEditable(int row, int column) {
return false;
}
}
然後在程式中使用MyTable代替DefaultTableModel建立對象就可以實現表格的不可編輯。當然,如果一開
始就使用AbstractTableModel這個抽象類別來建立的模型,那麼就不需要上面的方法,因為這個抽象類別預設
建立的表格就是不可編輯的。(注意,這裡提到的表格不可編輯,並不是表格中setEnabled()這個方法設
置的編輯屬性。這個方法設定的不可編輯,即整個表格的資料項目都不能進行選擇,這樣也無法知道我們選
中的是哪個資料項目。)這時,只需要加上滑鼠監聽器,並判斷e.getClickCount()是否等於2來觸發雙擊事
件。如果還想讓程式更完善的話,可以加上Timer做一個時間判斷,也就是當滑鼠點擊某項時,監聽在多
少毫秒之後如果還沒有進行第2次點擊,則視為單擊,否則就為雙擊。不過我們先做到這裡就夠了。java
沒有雙擊的事件還真是麻煩。
按右鍵事件:在按右鍵的事件中,要做出個判斷。當用左鍵選擇多項的時候,並在被選擇的資料項目中
點擊右鍵,那麼彈出右鍵菜單,並且程式會對這些被選中的資料項目進行操作,而且被選中的資料項目焦點不
會失去。這個就是多選的情況。如果在沒有被選中的資料項目上點擊右鍵,同樣彈出右鍵菜單,並且焦點變
到當前右鍵點擊的該資料項目上,即由多選轉換為單選。用文字表述出來確實很麻煩,如果用代碼就很簡單
,如:
//獲得當前滑鼠指向的資料項目的行號
int i = table.rowAtPoint(e.getPoint());
//判斷當前行是否被選中
if(!table.isRowSelected(i))
{
//如果沒有被選中,那麼將焦點移動到該資料項目上
table.setRowSelectionInterval(i, i);
}
//判斷是否是右鍵點擊
if(SwingUtilities.isRightMouseButton(e))
{
//在當前滑鼠的座標點上快顯功能表
jPopupMenu.show(e.getComponent(), e.getX(), e.getY());
}
剩下就是全選,全部刪除,清空這些功能都比較簡單。全選的話就首先使用getRowCount()獲得當前表格中的資料項目總行數,使用setRowSelectionInterval()方法,裡面的參數是0和獲得總行數,這樣就能選中
當前表格中所有的資料。全部刪除的話,首先是調用刪除類,在檔案中把所有資料刪除,然後在表格中使
用setDataVector()方法,把裡面的第一個參數數組設定為空白數組就可以了。清空則就不進行檔案的刪除
,只進行表格式資料的清空就可以實現,方法同理。
關於對資料的作業碼很簡單,大量的使用了setProperty()和getProperty()方法,這些比較常用的方法
我將他們封裝到一個類裡面,這樣便與操作。整個程式中比較麻煩一點的是在刪除的功能上。尤其是在前
面提到過對錶格中顯示的資料進行更新,文字也無法說清楚了,還是先把我寫的這段代碼貼出來,這個方
法也許是很笨的方法,不過限於自身能力,也只能做到這個程度了。
看這段代碼可能比較抽象,先舉個實際例子吧。比如當前我們有10條資料,每條資料都有13個欄位,然後
每條資料的第1個欄位是序號,要求就是當刪除其中某個資料後,該資料後的的所有資料的序號向前
移動一位,並且可以同時刪除多項,多項資料的序號可能不是連貫的。比如現在在螢幕的表格中只有10條
資料中的5條,他們的序號分別是1,3,5,8,9,這時我們要同時刪除序號為3和8的資料,當刪除後,屏
幕上剩下的資料序號應該是1,4,7,下面的代碼就是實現這個功能。
//刪除類的一個方法,該方法參數是資料轉送類,用來傳輸資料。方法執行完後返回一個結果數組。
public Object[][] removeTableData(DataStorage storage){
//獲得被選擇的資料項目的第一個欄位,也就是被選擇的資料的序號。因為可能存在多選,所以使用一個
數組儲存他們,比如是5和8。
Object [] data = storage.getData();
//獲得當前表格中所有資料項目的所有欄位,因此是個二維數組
Object [][] tableData = storage.getTableData();
//長度計數器,因為當每找到一項,刪除之後,所有資料的長度應該減少一位,由此計數器進行累計。
int delColumn = 1;
//拿選中的資料項目的序號與所有資料的序號進行比較,選中的序號數組,從數組中的最後一個元素開始
比較
for(int j = data.length - 1;j >=0;j--)
{
//表格中所有資料的序號,從選中的序號數組最後一位與表格中所有資料的序號數組的最後一位開始比
較
for(int i = tableData.length - delColumn;i >= 0;i--)
{
//如果兩者相等,就將此序號作為迴圈的起點,依次將後面一個資料項目覆蓋到前面一個資料項目
if(tableData[i][0].equals(data[j]))
{
for(;i < tableData.length - delColumn;i++)
{
for(int column = 0;column < 13;column++)
{
tableData[i][column] = tableData[i+1][column];
}
//每覆蓋完一個資料項目,將覆蓋過的該項資料的序號-1
int index = Integer.parseInt(tableData[i][0].toString());
tableData[i][0] = String.valueOf(index-1);
}
//到刪除完一項後資料的總長度應該減少1個,並從下一個資料開始進行比較
delColumn++;
break;
}
}
}
//建立一個數組,該數組的長度是刪除完選中的資料後剩下的資料個數。
Object [][] passData = new String[tableData.length - data.length][13];
//實際上我們並沒有真正的刪除某個資料,而是將後面的資料依次向前面進行覆蓋。因此我們需要建立
一個新的數組,這個數組只留下我們需要的資料項目
//多餘的資料將被拋棄
for(int i = 0;i < passData.length;i++)
{
for(int j = 0;j < 13;j++)
{
passData[i][j] = tableData[i][j];
}
}
return passData;
}
最後將返回的數組放到表格中,這個方法看起來確實很繁瑣,但是我能想到方法也就只有這個了。相信肯
定還有更簡單的方法來實現這個功能。當然使用資料庫的方法就不提了。整個項目的總結差不多就到這裡
。接下來如果有時間,我會繼續拓展該項目的功能。比如加入多使用者,考慮到操作的資料過多的話,會有
個等待的時間,那麼這個時候可以加入進程條,來顯示進度完成的百分比。有機會我還會研究一下SWT,
將該程式的介面用SWT來完成,相信又會是一件另人興奮的事情。到時候再進行總結吧。
最後附上一個顯示日期的JComboBox類,這個類完全繼承JComboBox,通過日期類擷取本地時間然後對其進行格式化,以YYYY年MM月DD日的格式顯示在JComboBox下拉式清單裡面,如果有同學需要可以直接將下面的代碼儲存為一個java檔案,導包完成後,直接建立一個該類的對象,就可以加入到介面中,可以象設定
JComboBox屬性一樣的設定這個控制項的屬性。
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.swing.JComboBox;
public class ComboBoxDate extends JComboBox{
public ComboBoxDate(){
super(getArray());
}
private static Object[] getArray(){
//聲明Object數組,用來儲存格式化後的日期文字
Object [] editDate = new String[1];
Calendar date = new GregorianCalendar();
//對擷取的本地時間進行格式化
SimpleDateFormat format = new SimpleDateFormat("yyyy年M月dd日");
editDate[0] = format.format(date.getTime());
return editDate;
}
}
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
public class tableTest {
private static JTable table;
public static void main(String[] args) {
tabFrame frame = new tabFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class tabFrame extends JFrame {
private JTable table;
public tabFrame() {
setTitle("表格模型測試");
setSize(400, 300);
getContentPane().setLayout(null);
final JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(65, 10, 255, 201);
getContentPane().add(scrollPane);
TableModel model = new InvestmentTableModel(30, 5, 10); //建立表格模型對象
table = new JTable(model); //建立表格,載入表格模型
scrollPane.setViewportView(table); //在捲動方塊中加入表格
}
}
/**
* 表格模型類
*/
class InvestmentTableModel extends AbstractTableModel {
private int years;
private int minRate;
private int maxRate;
private static double INITIAL_BALANCE = 100000.0;
public InvestmentTableModel(int y, int r1, int r2) {
years = y;
minRate = r1;
maxRate = r2;
}
/**
* 設定行數
*/
public int getRowCount() {
return years;
}
/**
* 設定列數
*/
public int getColumnCount() {
return maxRate - minRate + 1;
}
/**
* 填充各儲存格值
*/
public Object getValueAt(int r, int c) {
double rate = (c + minRate) / 100.0;
int nperiods = r;
double futurBalance = INITIAL_BALANCE * Math.pow(1 + rate, nperiods);
return String.format("%.2f", futurBalance);
}
/**
* 設定列名
*/
public String getColumnName(int c) {
return (c + minRate) + "%";
}
}