从7月14日开始来上海实习已经3个星期了,项目还没正式开始,前期工作准备了很多,而我主要是负责GUI这块,工具是Swing,所以陪伴Swing也已经快一个月的日子了.项目下个星期正式启动,所以对前面的体会作个小小的总结.
以前在inforsense公司的KDE平台上已经有一个Table Editor,点击主界面上含有表格数据的节点,可以打开表格,然后可以对各种表格进行编辑,增删等简单操作,而同时这些操作也会反映到主界面上的工作流模型中.现在的目标是对这个工具进一步扩展其功能,不仅融如Excel spreadsheet的功能(过滤,对cell进行编辑等),还有将树图与表格视图连接起来,可以进行两种视图之间的拖拽(dnd),切换等.现在更要与化学专业结合起来,本来这款软件是为化学家设计的,目的使他们操作起来更方便.所以还要把扩展后的表格编辑器和Interactive Browser结合起来,做到对同一组数据的多种视图,而且它们是同步的.比如表格中会有Structure(化学分子结构),分子量这样的特定的域,而点击后可启动特定的编辑化学分子结构的软件进行编辑,同时变化反映在表格数据中.
而我接触的都是Swing,它给我的感觉虽然好象仅仅是在AWT的类前面都加上了个J,但仔细研究,里面有各种设计模式的存在,这一点让我兴奋不已,正好借这个机会学习设计模式.我大部分时间接触的都是JTable和JTree.所以主要谈谈他们.Swing基本是就是个MVC的设计架构,就拿JTable来说,JTable就是View的部分,而TableModel就是M的部分.下面一点点讲讲实现的细节:
1.Filter(过滤器):
要在表格中实现过滤的功能,而实际上不影响原来的模型,可以考虑在原来的模型增加一个过滤器.它其实上也是一个TableModel(可以子类化TableModel的实现框架AbstractTableModel.),它把原来的TM作为自己的成员,任何实际的操作如getColumnCount(),getRowCount(),getColumnName()等都交给原来的TM来完成(调用TM的相应方法),只是在应该控制的地方控制一下,比如,getValueAt(i,j)就通过控制i,j来只返回过滤器想显示的行或列的数据,而具体的返回数据的操作还是由TM来完成.对setValueAt(),isCellEditable()也是同样的道理.我具体的做法就是用一个List把我想显示的行(列)号保存下来,在getValueAt(i,j)中,i的取值范围就是这个List了.这其实是一种Adapter模式的思想.同样,实现Sort也可以用这种方式.
2)Selection:
JTable中的选择都是由ListSelectionModel来完成的,行列都有默认的选择模型,访问行的SelectionModel的方式是getSelectionModel(),访问列的SelectionModel的方式是getColumnModel().getSelectionModel().你也可以实现自己的选择模型.可以通过
getRowSelectionAllowed()和getColumnSelectionAllowed()获取现在行列是否可选的信息,如果都可选,则在Cell级别是可选的.这就是为什么在行列都可选的情况下,设置i行被选中setRowSelectionInterval(i),同时设置j列被选中setColumnSelectionInterval(j),这样只有(i,j)的Cell单元被选中得到原因.但是反过来,如果我只想使(i,j)的Cell不被选中,而仅仅靠removeColumnSelectionInterval(j)和removeRowSelectionInterval(i)是实现不了的.这难道是Swing的漏洞?
前面已经讲到,设置改变选择状态主要是通过行列SelectionModel的setSelectionInterval(),addSelectionInterval(),removeSelectionInterval()三个方式实现的.
3)header
表的行,列的表头着实让我头痛了一阵.尤其是row header.我的row header是用一个JTable实现的,关键是要和表格同步起来.可以考虑与表格共用一个Filter,关键是改写getValueAt()和getRowCount()这两个方法.这样表格过滤留下的行也是表头这个JTable中所需要留下的行.而选择的同步则是覆盖changeSelection()这个方法实现的.而操作的方法就是在2)中提到的那几个方法.设置rowHeader为表头只需要在JScrollPane中用setRowHeaderView()指定即可,而表格最左上角的单元(行表头的表头)用setCorner()指定.
ColumnHeader其实在JTable中已有实现,如果要通过单击列头来选择全列的话,实现的方法可通过在列头上添加一个MouseListener,然后在它的MouseClicked方法中进行选择的同步,其余步骤与行在changeSelection()中的类似,有一点值得注意,要获取单击的列的索引是通过getTableHeader()后得到的tableHeader.columnAtPoint(e.getPoint())得到的,这里e是MouseEvent,也就是这个单击的动作事件.
具体的控制代码如下:
/**
* once click on the header, that column should be selected
*/
public void mouseClicked(MouseEvent e) {
JTableHeader header = table.getTableHeader();
TableColumnModel columns = header.getColumnModel();
if(!columns.getColumnSelectionAllowed())
return;
//get the column index being clicked
int column = header.columnAtPoint(e.getPoint());
if(column == -1)
return;
int count = table.getRowCount();
//set the entire column to be selected
if(count != 0)
table.setRowSelectionInterval(0,count-1);
ListSelectionModel selection = columns.getSelectionModel();
//if the shift modifier is pushed down, need to select multiple columns
if(e.isShiftDown()) {
int anchor = selection.getAnchorSelectionIndex();// the first index
int lead = selection.getLeadSelectionIndex();//the last index
if(anchor != -1) {
boolean old = selection.getValueIsAdjusting();
selection.setValueIsAdjusting(true);
boolean anchorSelected = selection.isSelectedIndex(anchor);
if(lead != -1) {
if(anchorSelected)
selection.removeSelectionInterval(anchor,lead);
else
selection.addSelectionInterval(anchor,lead);
}
if(anchorSelected)
selection.addSelectionInterval(anchor,column);
else
selection.removeSelectionInterval(anchor,column);
selection.setValueIsAdjusting(old);
}
else
//select single column
selection.setSelectionInterval(column,column);
}
else if(e.isControlDown()) {
if(selection.isSelectedIndex(column))
selection.removeSelectionInterval(column,column);//unselect this column
else
selection.setSelectionInterval(column,column);
}
else {
selection.setSelectionInterval(column,column);
}
}
4)dnd:
构造一个Transferable对象,保存传送的数据.而两方分别实现自己的TransferHandler即可.
5)表示器和编辑器.
如果想在JTree中添加JCheckbox,其实只需要实现自己的CellRenderer和CellEditor,在getTreeCellRendererComponent(Object value)和setTreeCellRendererComponent(Object value)中返回或设置一个JCheckBox(value.toString())即可.value就是Tree中节点node的UserObject.如果你想更改树中显示的文字,比如在父节点中显示子节点的数量,只需要在TreeNode类中(子类化DefaultMutableTreeNode)改写toString()方法即可.
目前的代码可以在"文件"中下载.