TableModel.JTable和AbstractTableModel

来源:互联网 发布:电商美工教程 编辑:程序博客网 时间:2024/05/16 05:36

http://blog.csdn.net/yakihappy/article/details/4073010


建立一个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) + "%"; }}