用Java制作属性编辑器 (转)

用Java制作属性编辑器
由  黄 海黉   发表于  2006年 4月 2日

注:本文原于2002年发表在《计算机世界网》上,在发表时可能由于编辑的疏忽,文章附带的源代码没有刊登出来,原文请参考链接。自文章发表后,很多读者给我发来Email问我要代码。我都一一给他们发了过去。今天有空,把文章稍作整理,和源代码一起重新放在这里,希望能对看到的朋友有所帮助。

作者长期从事网管软件的开发工作,网络设备的配置管理模块,需要对网络设备的大量参数进行配置工作,设计、开发配置管理模块的界面成为整个网管系统的难点,尤其是用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);
   }
   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;
      }
   }
}

好了,属性值编辑器的大框架已经完成了,下面以整数型的单元编辑器为例,简单说明单元编辑器的制作方法,双精度型和字符串型的和它类似,最复杂的用户自定义对话框型的,留待你自己看源代码吧(反正源代码里面有详细的注释的:-))。

/**
 * 创建并初始化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(""))
            return (Long)null;
         else
            return new Long(longTextField.getText());
      }
      public Component getTableCellEditorComponent(JTable table,
                                        Object value,
                                        boolean isSelected,
                                        int row,
                                        int column) {
         Component c = super.getTableCellEditorComponent
                                   (table, value, isSelected, row, column);
         previousValue = value;  // 开始编辑前记下初始值
         return c;
      }

      public boolean stopCellEditing() {
         Long lv = null;
         if(!longTextField.getText().equals("")) {
            try {
               lv = new Long(longTextField.getText());
            }catch(Exception e) {
               // 用户输入了非数字,则还原到未编辑状态
               cancelCellEditing();
               return true;
            }
         }
         if((previousValue == null) ?
                         (previousValue == lv)
                       : previousValue.equals(lv)) {
            cancelCellEditing(); // 修改前后的值相等则认为属性值未修改
            return true;
         }
         return super.stopCellEditing();
      }
   };
   longEditor.setClickCountToStart(2);

   // 控件失去焦点时,停止编辑,接受已输入的数据
   longTextField.addFocusListener(new FocusAdapter() {
      public void focusLost(FocusEvent e) {
         longEditor.stopCellEditing();
      }
   });

   // 渲染器用系统默认的JLabel,但字符靠左
   longRenderer = (DefaultTableCellRenderer)
                           this.getDefaultRenderer(Long.class);
   longRenderer.setHorizontalAlignment(JLabel.LEFT);
}

当属性值被修改了的时候,程序员可能想知道哪个值被修改了,修改后的新值是什么,然后可以做一些界面上的控制。那需要我们为属性值编辑器增加监听器,以便及时通知实现了监听器接口的类。

先定义一个监听器列表:

protected EventListenerList propertyListeners = new EventListenerList();

/**
 * 增加一个属性值变化监听器
 */
public void addPropertyUpdateListener(PropertyUpdateListener l) {
   propertyListeners.add(PropertyUpdateListener.class, l);
}

/**
 * 表格数据变化,由表格模型触发
 */
public void tableChanged(TableModelEvent e) {
   super.tableChanged(e);
   // 如果是属性值的列发生数据变化,要通知属性值变化监听器
   if(e.getType() == TableModelEvent.UPDATE
      && e.getFirstRow() == e.getLastRow()
      && e.getColumn() == 1) {
      String pName = (String)ptm.getValueAt(e.getFirstRow(), 0);
      Object nValue = ptm.getValueAt(e.getFirstRow(), 1);
      firePropertyUpdated(pName, nValue);
   }
}

/**
 * 通知属性值变化监听器
 * @param propertyName 属性名
 * @param newValue 属性变化后的新值
 */
protected void firePropertyUpdated(String propertyName, Object newValue) {
   Object[] listeners = propertyListeners.getListenerList();
   for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == PropertyUpdateListener.class) {
         ((PropertyUpdateListener)listeners[i + 1])
                              .propertyUpdated(propertyName, newValue);
      }
   }
}

总结

本文介绍了一种应用广泛的基于Swing的java界面控件PropertyEditor的设计思路和编写过程,读者可以从中体会到MVC模式把模型和视 图分开的优点,并能加深对java语言中的重要特性——接口的认识。对本文所附的代码,读者可以任意引用。欢迎读者与我联系,共同探讨java技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值