作者长期从事网管软件的开发工作,网络设备的配置管理模块,需要对网络设备的大量参数进行配置工作,设计、开发配置管理模块的界面成为整个网管系统的难点,尤其是用Java语言开发时,需要编制大量的界面布局、事件处理等代码,网管软件开发的主要工作量就集中在此。制作一个类似于Delphi、JBuilder等可视化开发工具的对象查看器的参数配置控件用于项目的优点是显而易见的:
1、界面显得很专业;
2、容易做到使整个系统的风格趋于一致;
3、使用灵活,代码量大大减小;
4、……
最终的使用效果如下图所示:
作者本人把它称为属性编辑器,它的主要特征是:是一个两列多行的表格,第一列用来显示属性名称,第二列用来显示和编辑属性值;属性值的显示和编辑可以有多种方式,如文本框输入、下拉框选择、组合框选择、自定义的弹出式对话框等;当属性被编辑后,可以向感兴趣的对象发出通知。
下面就属性编辑器的设计思路和编制过程进行具体的解释说明。
设计思路
根据属性编辑器的主要界面特征,选择JTable作为编辑器的基类。JTable是Swing中最复杂的组件之一,它主要被用来显示数据行和数据列,它可以为每个数据单元分别提供绘制器和编辑器,是典型的MVC(模型Model、视图View、控制Control)模式的实现者。
设计属性编辑器为一个两列的JTable,每一行数据的第一列存放不重复的字符串,作为属性的名称;第二列保存Object对象,根据其具体数据类型,设置其单元绘制器和单元编辑器,一般单元绘制器用系统默认的JLabel即可,而单元编辑器则必须提供定制的控件,如对字符串型数据,用JTextField或者JComboBox;对布尔型数据,用JCheckBox;对特殊类型,可以提供JDialog,对话框的主要界面可以由使用本属性编辑器的程序员自行定制。
属性编辑器还要提供相关接口,如属性值是否只读;属性值变化时必须通知监听者;等等;
属性编辑器的具体设计,请参见下图,限于篇幅,该图只简单列举了几个主要的类之间的关系和一些重要的变量和方法:
属性编辑器的制作过程
属性编辑器是从JTable上继承下来的,必须给它定义一个DefaultTableModel的子类,用来作为属性编辑器的数据模型,存放属性名和属性值,因为它只在属性编辑器内部使用,所以可以定义为属性编辑器的内部类:
public class PropertyEditor extends JTable {
protected class PropertyEditorModel extends DefaultTableModel{
public PropertyEditorModel() {
super(0, 2); // 只有两个列
}
public String getColumnName(int col) {
return " "; // 不需要列标题
}
public boolean isCellEditable(int row, int col) {
if(col == 0)
return false; // 第一列是属性名,不可编辑
else
// 属性值是否可编辑要看用户指定的情况
return ((Boolean)propertyEditable.get(this.getValueAt(row, 0))).booleanValue();
}
}
}
要实现定制的单元绘制器和编辑器,必须覆盖JTable的getCellEditor和getCellRenderer方法,那些已经做好的绘制器、编辑器和该属性值是否允许编辑都可以根据属性名保存在Hashtable里,需要的时候根据属性名取出来:
/**
* 每一个属性项都对应一个单元编辑器,用Hashtable来保存这些编辑器
*/
protected Hashtable propertyEditors = new Hashtable(10);
/**
* 每一个属性项都对应一个单元渲染器
*/
protected Hashtable propertyRenderers = new Hashtable(10);
/**
* 属性是否可编辑
*/
protected Hashtable propertyEditable = new Hashtable(10);
/**
* 获取指定单元格的编辑器
* @param row 行
* @param col 列
*/
public TableCellEditor getCellEditor(int row, int col) {
TableCellEditor editor = null;
if(col == 1) { // 属性值列才需要编辑器。这个判断条件不要也可,效率会低一点。
editor = (TableCellEditor)propertyEditors.get(this.getValueAt(row, 0));
}
if(editor == null) { // 没找到编辑器,则用系统默认的。
editor = super.getCellEditor(row, col);
}
return editor;
}
/**
* 获取指定单元格的渲染器
*/
public TableCellRenderer getCellRenderer(int row, int col) {
TableCellRenderer renderer = null;
if(col == 1) {
renderer = (TableCellRenderer)propertyRenderers.get(this.getValueAt(row, 0));
}
if(renderer == null) {
renderer = super.getCellRenderer(row, col);
}
// 给表格元素提供Hint提示
if(renderer instanceof JComponent) {
Object v = this.getModel().getValueAt(row, col);
if(v == null) { // 属性值有可能为空,则取属性名;属性名必不为空。
v = this.getModel().getValueAt(row, 0);
}
((JComponent)renderer).setToolTipText(v.toString());
}
return renderer;
}
如何确定哪个属性用哪一种编辑器呢?可以根据用户程序员传入的参数来确定,对传入的整数型数据,则用LongCellEditor;字符串型的当然用StringCellEditor了,其它依次类推。以整数型来举例:
/**
* 在属性表中增加整数属性,允许为空值,编辑器和渲染器为long型编辑器和渲染器。
* 当属性值为空值时,必须写成:
* addProperty("pName", (Long)null)
* @param propertyName 属性名
* @param longNumObj 属性初始值
*/
public void addProperty(String propertyName, Long longNumObj) {
if(propertyName == null) throw new RuntimeException("Coding error : property name can NOT be null !");
Object[] row = new Object[2];
row[0] = propertyName;
row[1] = longNumObj;
appendRow(row); // 往表格增加行
propertyEditors.put(propertyName, longEditor); // 添加整型编辑器
propertyRenderers.put(propertyName, longRenderer); // 添加整型绘制器
propertyEditable.put(propertyName, new Boolean(true)); // 设置该属性允许编辑
}
给属性编辑器加上get和set接口:
/**
* 根据属性名得到属性值
* @param propertyName 属性名
*/
public Object getPropertyValue(String propertyName) {
Object retValue = null;
for(int i = 0; i < ptm.getRowCount(); i++) {
if(ptm.getValueAt(i, 0).equals(propertyName)) {
retValue = ptm.getValueAt(i, 1);
break;
}
}
return retValue;
}
/**
* 设置属性值
* @param propertyName 属性名
* @param newValue 新的属性值
*/
public void setPropertyValue(String propertyName, Object newValue) {
for(int i = 0; i < ptm.getRowCount(); i++) {
if(ptm.getValueAt(i, 0).equals(propertyName)) {
ptm.setValueAt(newValue, i, 1);
break;
}
}
}
好了,属性值编辑器的大框架已经完成了,下面以整数型的单元编辑器为例,简单说明单元编辑器的制作方法,双精度型和字符串型的和它类似,最复杂的用户自定义对话框型的,留待读者自己看源代码吧(反正源代码里面有详细的注释的J)。
/**
* 创建并初始化long型数据的编辑器和渲染器
*/
private void createLongEditorRenderer() {
final JTextField longTextField = new JTextField("0", 5); // 用文本输入框做输入控件
longTextField.setHorizontalAlignment(JTextField.LEFT);
longEditor = new DefaultCellEditor(longTextField) {
private Object previousValue = null;
public Object getCellEditorValue() {
if(longTextField.getText().equals("")