第5章 JavaBean开发

代码复用一直是开发人员追求的一个目标,组件技术是实现代码复用的一个很好途径。目前流行的组件技术有COM、DCOM、CORBA等。JavaBean是利用Java实现的组件技术,具有Java语言所有的特点,如易用性以及平台无关性。JavaBean的设计目标就是一次性编写、任何地方执行、任何地方重用。

本章主要介绍如何在NetBeans中开发和利用JavaBean组件。首先介绍JavaBean的基本知识,然后通过实例介绍如何开发一个简单的JavaBean,以及如何使用这个JavaBean组件。接着,逐步深入给出可视化JavaBean的开发方法,如何为JavaBean添加属性等内容。在学习本章前,建议读者先认真学习一下JavaBean开发的相关知识。

本章主要内容

● JavaBean的基本概念

● 如何创建和使用一个简单的JavaBean

● JavaBean的属性

● 如何使用下拉列表框形式的属性编辑器

● 如何绘制属性

● 如何自定义属性编辑器

本章亮点

● 可视化JavaBean的创建和使用方法

● 如何在“属性”编辑器中绘制属性

● 如何定制自己的属性编辑器

5.1 JavaBean概述

在软件开发中,组件技术是非常重要的一项技术。利用组件,开发人员可以像搭积木一样把软件功能作为一个组件来快速地组装成一个应用程序。组件技术的一个优点就是可以复用,即一个组件可以被不同的系统和平台使用。JavaBean是一种专门为当前开发人员设计的全新的组件技术,它为软件开发者提供了一种极佳的问题解决方案。JavaBean具有如下的特点。

● 完全的可移植性:JavaBean是使用Java语言实现的,众所周知,Java语言具有很好的可移植性和跨平台性,因此这为JavaBean提供了独立于平台的组件解决方案。JavaBean可以在任何支持Java的系统中无须修改地执行,提供了高度的可移植性。

● 分布式计算支持:JavaBean提供了分布式计算的能力,使得开发者可以在任何时候使用分布式计算机制。

● 应用程序构造器支持:JavaBean体系结构支持指定设计环境属性和编辑机制,这使得开发者可以使用可视化应用程序构造器无缝地组装和修改JavaBean组件。例如在NetBeans集成开发环境下,就可以可视化地创建一个JavaBean组件。

JavaBean组件技术不仅可以应用于传统的桌面应用程序开发,还可以与JSP配合开发Web应用程序,以及在企业级开发中(Enterprise JavaBean)使用。

NetBeans中提供了对开发JavaBean组件的很好支持,可以快速地创建并使用JavaBean组件。在下面各节中将详细介绍在NetBeans中开发JavaBean的方法。

5.2 如何创建一个简单的JavaBean

其实,JavaBean也是普通的Java对象,只不过它遵循了一些特别的约定而已。NetBeans集成开发环境提供了创建JavaBeans组件的模板。这些模板为非可视Bean提供了框架代码。也可以使用JFC/Swing模板来创建可视Bean。

5.2.1 创建一个简单的JavaBean

在NetBeans中,可以把JavaBean添加到组件面板中,以便于代码重复利用。下面是创建一个简单JavaBean的过程。

① 创建一个Java标准项目,项目的名字为“SimpleBeanDemo”。

② 创建一个包,名字为“mybeans”。

③ 选择“文件”→“新建文件”命令,打开“新建文件”对话框。在“类别”窗口中选择“Java”节点,如图5-1所示。

图5-1 “新建文件”对话框的“Java类”类别

④ 在“文件类型”窗口中选择“Java类”,然后单击“下一步”按钮,打开“新建Java类”对话框,如图5-2所示。

图5-2 “新建Java类”对话框

⑤ 在“类名”文本框中输入“MySimpleBean”,在“包”下拉列表框中选择包“mybeans”。单击“完成”按钮,创建一个Java类,也就是创建一个JavaBean。

⑥ 在源代码编辑器中,为MySimpleBean类添加一个String类型的name属性,并添加一个getName方法用于读取name的值;添加一个setName方法设置name的值。具体实现代码如下所示:

            /*
            * MySimpleBean.java
            *
            */
            package mybeans;
            /**
            *
            * @author Liu Bin
            */
            public class MySimpleBean {
                private String name;
                /** Creates a new instance of MySimpleBean */
                public MySimpleBean() {
                  name = "Hello World";
                }
                public String getName() {
                  return name;
                }
                public void setName(String name) {
                  this.name = name;
                }
            }

⑦ 在“项目”窗口中选中“MySimpleBean.java”文件,然后选择“工具”→“添加到组件面板”命令,打开“选择组件面板类别”对话框,如图5-3所示。

图5-3 “选择组件面板类别”对话框

⑧ 选中“Bean”类别,单击“确定”按钮,把这个JavaBean添加到“Bean”类别中。此时,会在“组件面板”窗口中看到刚刚创建的MySimpleBean,如图5-4所示。至此,完成了一个简单的JavaBean的创建。

图5-4 把MySimpleBean组件添加到了组件面板

说明:新创建的Bean类需要经过编译以后才能使用。

5.2.2 如何使用JavaBean

上面介绍了如何创建一个简单的JavaBean,下面具体演示如何在Java项目中使用这个JavaBean。

① 确认上一节中创建的项目“SimpleBeanDemo”为主项目,如果不是主项目就设置为主项目。

② 选择“文件”→“新建文件”命令,打开“新建文件”对话框。在“类别”窗口中选择“Swing GUI窗体”节点,在“文件类型”窗口中选择“JFrame”窗口。单击“下一步”按钮,打开“新建JFrame窗体”对话框,如图5-5所示。

图5-5 “新建JFrame窗体”对话框

③ 在“类名”文本框中输入“UseSimpleBean”,清除“包”下拉列表框中的内容,让这个窗体创建在与JavaBean不同的位置。单击“完成”按钮创建“JFrame”窗体。

④ 在GUI设计器中,右键单击JFrame窗体,选择弹出菜单中选择“从组件面板上添加”→“Bean”→“MySimpleBean”命令,如图5-6所示。把MySimpleBean添加到这个窗体JFrame后,可以在“检查器”窗口中看到添加的MySimpleBean,如图5-7所示。

图5-6 添加MySimpleBean组件

图5-7 “检查器”窗口中的MySimpleBean

⑤ 选中这个JavaBean组件后,可以在“属性”窗口中修改组件的name属性,如图5-8所示。

图5-8 “属性”窗口修改组件的属性

至此,就可以在这个新的程序中使用创建的JavaBean组件了。

5.3 创建和使用可视化的JavaBean组件

上一节中创建了一个简单的JavaBean组件,这个组件没有显示界面,只有一个name属性。本节将介绍如何创建一个可视化的JavaBean组件,并在程序中使用这个可视化的JavaBean组件。同时,值得一提的是,本节中的程序都是通过鼠标操作完成,请读者注意GUI设计器中连接模式的使用方法。

5.3.1 创建可视化的JavaBean组件

在Swing组件中,有一个进度栏(JProgressBar)组件。该组件可以在进度条的中央显示当前的进度值,如图5-9所示。

图5-9 JProgressBar组件

JProgressBar的进度显示值,只能显示在进度条的中央,不能在其他位置。下面将创建一个可视化的JavaBean,把进度值显示在其他的位置,其操作步骤介绍如下。

① 创建一个Java标准项目,项目的名字为“GuageBean”。

② 创建一个包,名字为“guagebean”。

③ 选择“文件”→“新建文件”命令,打开“新建文件”对话框。在“类别”窗口中选择“Swing GUI窗体”节点,如图5-10所示。

图5-10 “新建文件”对话框

④ 在“文件类型”窗口中选择“JPanel窗体”,单击“下一步”按钮,打开“新建JPanel窗体”对话框,如图5-11所示。

图5-11 “新建Jpanel窗体”对话框

⑤ 在“类名”文本框中输入“MyGuage”,在“包”下拉列表框中选择“guagebean”包。单击“完成”按钮,创建一个可视化的JavaBean。

⑥ 在GUI设计器中添加一个JLabel组件,这个组件用于显示进度条的值。双击JLabel组件,把text值修改为0,如图5-12所示。

图5-12 添加一个JLabel组件

⑦ 在GUI设计器中添加一个JProgressBar组件,在“属性”窗口中把stringPainted属性选中。此时会在进度条的中央显示进度值字符串,如图5-13所示。

图5-13 添加一个JProgressBar组件

⑧ 在GUI设计器中,选中工具栏中的“连接模式”按钮。单击“检查器”窗口中的jProgressBar1组件,选择事件源组件。此时在GUI设计器中的进度条组件会变为红色,如图5-14所示。

图5-14 选择事件源组件

⑨ 在“检查器”窗口中单击jLabel1组件,打开“连接向导”的“选择源事件”对话框。在“源组件”文本框中给出了事件的源组件,在“事件”列表中可以选择源事件。在“事件”列表中选择“change”→“stateChanged [jProgressBar1StateChanged]”节点,如图5-15所示。

图5-15 “连接向导”的“选择源事件”对话框

⑩ 单击“下一步”按钮,打开“连接向导”的“指定目标操作”对话框。在“目标组件”文本框中显示了源组件发生源事件时要操作的目标组件。“设置属性”单选按钮用于在下面的列表框中显示目标组件的属性;“方法调用”单选按钮用于在下面的列表框中显示目标组件的成员方法;选中“用户代码”单选按钮后,可以在单击“完成”按钮后,在编辑器中处理事件的定制代码。这里选中“方法调用”单选按钮,在下面的列表框中选中setText(String)选项,如图5-16所示。

图5-16 “连接向导”的“指定目标操作”对话框

⑪ 单击“下一步”按钮,打开“连接向导”的“输入参数”对话框。在这个对话框中可以选择目标组件的方法调用的参数。这里选中“方法调用”单选按钮,如图5-17所示。

图5-17 “连接向导”的“输入参数”对话框

⑫ 单击“方法调用”单选按钮旁边的“…”按钮,打开“选择方法”对话框。在“组件”下拉列表框中选中“jProgressBar1”,在下面的“方法”列表中选择getString()选项,如图5-18所示。

图5-18 “选择方法”对话框

⑬ 单击“确定”按钮,返回“输入参数”对话框,单击“完成”按钮,完成代码的自动添加。这里添加了如下所示的代码:

              jProgressBar1.addChangeListener(new javax.swing.event.ChangeListener() {
                      public void stateChanged(javax.swing.event.ChangeEvent evt) {
                          jProgressBar1StateChanged(evt);
                      }
              });
              …
              private void jProgressBar1StateChanged(javax.swing.event.ChangeEvent evt) {
                  jLabel1.setText(jProgressBar1.getString());
               }

⑭ 在源代码编辑器中,添加如下所示的代码。其中,getValue()方法用于返回进度条的进度值;setValue()方法用于设置进度条的进度值。

              /**
               * 返回进度条的值
               * @return返回进度条的当前进度值
               */
              public int getValue() {
                  return jProgressBar1.getValue();
              }
              /**
               * 设置进度条的值
               * @param value进度条的新值
               */
              public void setValue(int value) {
                  jProgressBar1.setValue(value);
              }

⑮ 生成项目。在“项目”窗口中选中“MyGuage.java”文件,然后选择“工具”→“添加到组件面板”命令,打开“选择组件面板类别”对话框。选中“Bean”类别,单击“确定”按钮,把这个JavaBean添加到“Bean”类别中。此时,会在“组件面板”窗口中看到刚刚创建的MyGuage,如图5-19所示。至此,完成了一个可视化的JavaBean的创建。

图5-19 把MyGuage组件添加到了组件面板

5.3.2 使用可视化的JavaBean组件

使用JavaBean组件和使用一般的Swing组件没有什么区别。下面在上一节的“GuageBean”项目中,仿照第5.2.2 节的方法创建一个JFrame,名字为“MyGuageTest”。在JFrame中添加MyGuage组件。再添加一个JButton,修改text属性为“更新”,如图5-20所示。

图5-20 在JFrame中添加MyGuage组件

在GUI设计器中,选中工具栏中的“连接模式”按钮。单击“导航”窗口中的jButton1组件,选择事件源组件。再单击myGuage1组件,打开“连接向导”的“选择源事件”对话框。在“事件”列表中选择“action”→“actionPerfomed [jButton1ActionPerformed]”节点,如图5-21所示。

图5-21 “选择源事件”对话框

单击“下一步”按钮,打开“连接向导”的“指定目标操作”对话框。选中“方法调用”单选按钮,在下面的列表框中选中setValue(int)选项,如图5-22所示。这里的setValue方法就是上一节中添加的方法。

图5-22 “选择目标操作”对话框

单击“下一步”按钮,打开“连接向导”的“输入参数”对话框。选中“值”单选按钮,并在后面的文本框中输入“myGuage1.getValue()+1”,如图5-23所示。

图5-23 “输入参数”对话框

单击“完成”按钮,将自动添加如下所示的代码:

                jButton1.addActionListener(new java.awt.event.ActionListener() {
                  public void actionPerformed(java.awt.event.ActionEvent evt) {
                        jButton1ActionPerformed(evt);
                  }
                });
                ….
                private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
                  myGuage1.setValue(myGuage1.getValue()+1);
                }

运行程序,显示界面如图5-24所示。当单击“更新”按钮时,进度条中间和上方会显示当前的进度值。

图5-24 MyGuage组件的应用界面

5.4 JavaBean的属性

JavaBean都具有属性、事件和方法三个特性,其中属性的概念相当于面向对象的程序设计语言中对象的属性概念,再说得具体些就是JavaBean类的成员变量。JavaBean的属性包括Sample属性、Indexed属性、Bound属性和Constrained属性。

5.4.1 创建简单属性

在第5.2.1节创建的MySimpleBean组件中,name就是一个简单类型的属性。属性一般都有与之相对应的setter/getter方法。例如属性X对应的方法为setX/getX。其中,getX表示读取属性的值;setX表示设置属性的值。name属性对应的两个setter/getter方法如下所示:

                private String name;
                public String getName() {
                  return name;
                }
                public void setName(String name) {
                  this.name = name;
                }

只读属性是只包含getter获取方法的属性;只写属性是只包含setter设置方法的属性。如果同时包含getter和setter方法,则该属性既可以被读也可以被写。

5.4.2 Indexed属性

Indexed属性表示一个数组值,使用与该属性对应的setter/getter方法可以设置或者读取数组中某个元素的数值。另外,Indexed属性也可设置或读取整个数组的值。Indexed属性的定义如下所示:

                /**
                * 保存属性lines的值。
                */
                private String[] lines;
                /**
                * 属性lines的索引获取方法
                * @param index属性的索引
                * @return在 <CODE>index</CODE> 处属性的值
                */
                public String getLines(int index) {
                  return this.lines[index];
                }
                /**
                * 属性lines的获取方法
                * @return属性lines的值
                */
                public String[] getLines() {
                  return this.lines;
                }
                /**
                * 属性lines的索引设置方法
                * @param index属性的索引
                * @param lines在 <CODE>index</CODE> 处属性的新值
                */
                public void setLines(int index, String lines) {
                  this.lines[index] = lines;
                }
                /**
                * 属性lines的设置方法
                * @param lines属性lines的新值
                */
                public void setLines(String[] lines) {
                  this.lines = lines;
                }

5.4.3 Bound属性

Bound属性可以在NetBeans的属性编辑器中进行编辑,因此创建自己的组件时,一般都会使用Bound属性。下面的MyJavaBean组件就包含一个名字为sampleProperty的Bound属性。

            import java.beans.*;
            import java.io.Serializable;
            /**
            * @author Liu Bin
            */
            public class MyNewBean extends Object implements Serializable {
                public static final String PROP_SAMPLE_PROPERTY = "sampleProperty";
                private String sampleProperty;
                private PropertyChangeSupport propertySupport;
                public MyNewBean() {
                  propertySupport = new PropertyChangeSupport(this);
                }
                public String getSampleProperty() {
                  return sampleProperty;
                }
                public void setSampleProperty(String value) {
                  String oldValue = sampleProperty;
                  sampleProperty = value;
                  propertySupport.firePropertyChange(PROP_SAMPLE_PROPERTY, oldValue,
                                                        sampleProperty);
                }
                public void addPropertyChangeListener(PropertyChangeListener listener) {
                  propertySupport.addPropertyChangeListener(listener);
                }
                public void removePropertyChangeListener(PropertyChangeListener listener){
                  propertySupport.removePropertyChangeListener(listener);
                }
            }

其中,PropertyChangeSupport是一个实用工具类,支持Bound属性的Bean可以使用该类。可以使用此类的实例作为Bean的成员字段,并将各种工作委托给它。PropertyChangeSupport类是可序列化的,在对它进行序列化时,它将保存(并恢复)本身可序列化的所有侦听器。在序列化期间,将跳过所有不可序列化的侦听器。

方法firePropertyChange(PROP_SAMPLE_PROPERTY, oldValue, sampleProperty)报告所有已注册侦听器的Bound属性更新。如果新属性和旧属性相同并且是非空的,则不会触发事件。

方法addPropertyChangeListener(PropertyChangeListener listener)为JavaBean添加一个属性监听器,当属性发生变化后,JavaBean会通过调用firePropertyChange方法通知监听器的所有者。

方法removePropertyChangeListener(PropertyChangeListener listener)用于删除已经注册的监听器。

5.5 创建简单属性编辑器

在“属性”窗口中可以直接编辑Bean的一些简单类型的属性,例如String类型。第5.4.3节中创建的JavaBean就带有一个简单的属性:sampleProperty。下面在这个基础上创建一个可以添加到窗体上的Label,并且可以在属性编辑器中修改Label中的文字。

① 创建一个Java标准项目,项目的名字为“MyBeanPropertyEditors”。

② 创建一个包,名字为“mybeans”。

③ 按照第5.4.3节中介绍的方法创建一个名为“MyLabel”的Bean,并添加代码如下所示。

              /*
               * MyLabel.java
               *
               * Created on 2007年2月22日,下午9:06
               */
              package mybeans;
              import java.awt.Component;
              import java.awt.Dimension;
              import java.awt.Graphics;
              import java.beans.*;
              import java.io.Serializable;
              /**
               * @author Liu Bin
               */
              public class MyLabel extends Component implements Serializable {
                  public static final String PROP_TEXT = "text";
                  private String text = "MyLabel1";
                  private PropertyChangeSupport propertySupport;
                  public MyLabel() {
                    propertySupport = new PropertyChangeSupport(this);
                    this.setPreferredSize(new Dimension(60, 30));
                  }
                  public String getText() {
                    return text;
                  }
                  public void setText(String value) {
                    String oldValue = text;
                    text = value;
                    propertySupport.firePropertyChange(PROP_TEXT, oldValue, text);
                  }
                  public void addPropertyChangeListener(PropertyChangeListener listener) {
                    propertySupport.addPropertyChangeListener(listener);
                  }
                  public void removePropertyChangeListener(PropertyChangeListener listener){
                    propertySupport.removePropertyChangeListener(listener);
                  }
                  public void paint(Graphics g) {
                    if (text ! = null && text ! = "") {
                        g.drawString(text, 0, getHeight()-5);
                    }
                  }
              }

其中,MyLabel从Component派生。Component是一个具有图形表示能力的类,可在屏幕上显示,并可与用户进行交互。典型图形用户界面中的按钮、复选框和滚动条等都是从Component派生出来的。

在代码中实现了void paint(Graphics g)方法,该方法用于绘制组件。这里,绘制了属性text中的内容。

把MyLabel组件添加到“组件面板”窗口中,再添加到JFrame窗口中,显示界面如图5-25所示。新添加的text属性在“属性”窗口中的界面如图5-26所示。当在“属性”窗口中修改text属性后,MyLabel中的内容也会随之更新。

图5-25 MyLabel组件

图5-26 text属性在“属性”窗口中的界面

5.6 在“属性”窗口中实现下拉列表框

用户还可以自己定义属性编辑器,提供高级的属性编辑,例如下拉列表框形式的属性编辑器、在属性编辑器中绘制图形,甚至可以打开另外的对话框编辑属性。本节就讨论如何在“属性”窗口中实现下拉列表框。

当某个属性是枚举类型时(或者,属性值在一定的可选范围内),可以为该属性在“属性”窗口中添加下拉列表框,这样用户只能从给定的值中进行选择。下面通过一个具体的实例演示具体的实现过程。

5.6.1 创建MyShape组件

在第5.5节创建的MyBeanPropertyEditors标准项目中,创建一个名为“myshape”的包。在myshape包中创建一个MyShape组件,组件的代码如下所示。

            /*
             * MyShape.java
             *
             * Created on 2007年2月22日,下午9:16
             */
            package myshape;
            import java.awt.Component;
            import java.awt.Dimension;
            import java.awt.Graphics;
            import java.beans.*;
            import java.io.Serializable;
            /**
            * @author Liu Bin
            */
            public class MyShape extends Component implements Serializable {
                public static final String PROP_SHAPE = "shape";
                private String shape = "Rectangle";
                private PropertyChangeSupport propertySupport;
                public MyShape() {
                  propertySupport = new PropertyChangeSupport(this);
                  this.setPreferredSize(new Dimension(50, 50));
                }
                public String getShape() {
                  return shape;
                }
                public void setShape(String value) {
                  String oldValue = shape;
                  shape = value;
                  propertySupport.firePropertyChange(PROP_SHAPE, oldValue, shape);
                }
                public void addPropertyChangeListener(PropertyChangeListener listener) {
                  propertySupport.addPropertyChangeListener(listener);
                }
                public void removePropertyChangeListener(PropertyChangeListener listener){
                  propertySupport.removePropertyChangeListener(listener);
                }
                public void paint(Graphics g) {
                  int w = getWidth();
                  int h = getHeight();
                  if (shape == "Round Rectangle") {
                        g.drawRoundRect(0, 0, w-1, h-1, 30, 30);
                  } else if (shape == "Oval") {
                        g.drawOval(0, 0, w-1, h-1);
                  } else if (shape == "Arc") {
                        g.drawArc(0, 0, w-1, h-1, 0, 180);
                  } else {
                        g.drawRect(0, 0, w-1, h-1);
                  }
                }
            }

其中,属性shape有如下四个可选的值。

● Rectangle:表示当前形状为一个矩形。

● Oval:表示当前形状为一个椭圆。

● Arc:表示当前形状为一个弧线。

● Round Rectangle:表示当前形状为一个圆角矩形。

paint(Graphics g)方法会根据属性shape的值绘制当前的图形。

5.6.2 创建下拉列表框形式的属性编辑器

myshape包中创建一个ShapPropertyEditor类,用于创建下拉列表框形式的属性编辑器,代码如下所示:

            /*
             * ShapePropertyEditor.java
            *
            */
           package myshape;
           import java.beans.PropertyEditorSupport;
           /**
            *
            * @author Liu Bin
            */
           public class ShapePropertyEditor extends PropertyEditorSupport {
              public String[] getTags() {
                  String shapes[] = {
                      "Rectangle",
                      "Round Rectangle",
                      "Oval",
                      "Arc"
                  };
                  return shapes;
              }
              public String getJavaInitializationString() {
                  return "\"" + getAsText() + "\"";
              }
              public String getAsText() {
                  return (String) super.getValue();
              }
              public void setAsText(String string) throws IllegalArgumentException {
                  super.setValue(string);
              }
           }

其中,PropertyEditorSupport实现了PropertyEditor接口,声明如下所示:

            public class PropertyEditorSupport extends Object implements PropertyEditor {
                ...
            }

PropertyEditorSupport是一个帮助构建属性编辑器的支持类,为希望允许用户编辑某个给定类型的属性值的GUI提供支持。PropertyEditor提供了各种不同类型的显示和更新属性值的方式。

对于简单的属性,这里只需要实现getAsText和setAsText方法即可,其中getAsText方法用于返回当前属性值的字符串形式;setAsText方法用于以字符串的形式设置属性值。因为MyShape的shape属性就是一个简单的String类型,因此不需要做任何的转换,这里直接调用父类的方法进行处理即可。

说明:实际上,因为shape属性比较简单,所以没有getAsText和setAsText方法也是可以的。对于一些复杂的属性,则需要实现这两个方法。这里给出这两个方法的实现,供读者参考。

getTags方法用于返回属性的全部可选值,该方法返回的字符串数组将显示在“属性”窗口的属性值下拉列表框中。

getJavaInitializationString方法将在产生代码及设置属性值时被使用。该方法需要返回一段表示当前属性值的Java代码。这里,在属性值字符串两边添加了引号,这样使得整个属性值会被作为一个字符串处理。

5.6.3 将属性和属性编辑器关联

在创建了属性编辑器后,就需要把属性编辑器和属性关联起来。通常有两种关联的方式:通过PropertyEditorManager关联和通过BeanInfo关联,下面分别进行介绍。

1.通过PropertyEditorManager进行关联

PropertyEditorManager可用于查找任何给定类型名称的属性编辑器。此属性编辑器必须支持java.beans.PropertyEditor接口,以编辑给定的对象。

PropertyEditorManager提供一个registerEditor方法,允许专门为某一给定类型的属性注册编辑器,其语法定义如下所示:

            public static void registerEditor(Class<?> targetType,
                                          Class<?> editorClass)

参数targetType表示要被编辑的属性类型的Class对象;参数editorClass是编辑器类的Class对象。如果该参数为空,则将移除所有现有定义。例如要注册MyString类型属性的属性编辑器MyStringEditor,则代码如下所示:

            PropertyEditorManager.registerEditor(MyString.class,
                                          MyStringEditor.class);

使用PropertyEditorManager关联属性编辑器的优点是使用简单。但是缺点也是显而易见的,即不能为某一个特定的属性设置属性编辑器。

2.通过BeanInfo进行关联

BeanInfo可以对与其关联的JavaBeans组件的行为方式和显示方式进行说明。例如,一个JavaBean有多个属性和方法,那么使用BeanInfo可以指定哪些属性和方法可以被使用。下面介绍如何通过BeanInfo将MyShape的shape属性与shape属性编辑器ShapPropertyEditor进行关联。

在“项目”窗口中选中myshape包,然后创建一个Java类文件,并实现如下的代码:

            /*
             * MyShapeBeanInfo.java
             *
             */
            package myshape;
            import java.beans.BeanDescriptor;
            import java.beans.IntrospectionException;
            import java.beans.PropertyDescriptor;
            import java.beans.SimpleBeanInfo;
            import mybeans.*;
            /**
             *
             * @author Liu Bin
             */
            public class MyShapeBeanInfo extends SimpleBeanInfo {
                private final static Class beanClass = MyShape.class;
                public BeanDescriptor getBeanDescriptor() {
                  BeanDescriptor bd = new BeanDescriptor(beanClass);
                  bd.setDisplayName("MyShapeBean");
                  return bd;
                }
                public PropertyDescriptor[] getPropertyDescriptors() {
                  try {
                        PropertyDescriptor textPD =
                                new PropertyDescriptor("shape", beanClass);
                        textPD.setPropertyEditorClass(ShapePropertyEditor.class);
                        PropertyDescriptor rv[] = {textPD};
                        return rv;
                  } catch (IntrospectionException e) {
                        throw new Error(e.toString());
                  }
                }
            }

自己的BeanInfo类要从SimpleBeanInfo类派生。getBeanDescriptor方法由于返回一个包含Bean信息的BeanDescriptor对象,在BeanDescriptor对象中可以设置Bean的显示名字(displayName)等信息。如果希望Bean信息通过自动分析获得,则方法应该返回空。

这里覆盖了SimpleBeanInfo的getPropertyDescriptors方法,用于指定哪些属性可以被显示。getPropertyDescriptors方法返回Bean支持的可编辑属性的PropertyDescriptor数组。下面的代码创建了一个PropertyDescriptor对象,用于表示MyShape的shape属性。

                        PropertyDescriptor textPD =
                                new PropertyDescriptor("shape", beanClass);

下面的语句为属性shape指定了属性编辑器ShapePropertyEditor,并生成一个Property Descriptor数组。

              textPD.setPropertyEditorClass(ShapePropertyEditor.class);
              PropertyDescriptor rv[] = {textPD};

这样,就把属性shape和属性编辑器ShapePropertyEditor关联起来了。当把这个Bean添加到窗体上时,显示界面如图5-27所示。MyShape Bean的shape属性及其属性编辑器如图5-28所示。

图5-27 MyShape Bean的显示界面

图5-28 MyShape的shape属性编辑器

5.7 绘制属性编辑器

当属性是图形时,需要在“属性”窗口中绘制属性值,例如绘制背景颜色。本节将讨论如何自己绘制属性编辑器。

PropertyEditorSupport类的isPaintable方法用于决定属性编辑器是否支持自定义绘制(即是否支持painValue方法),其语法定义如下所示:

            public boolean isPaintable()

如果希望进行自定义绘制,那么需要在派生类中覆盖这个方法,并使得方法的返回值为true。另外,还需要覆盖PropertyEditorSupport类paintValue方法,以绘制属性。paintValue方法的语法定义如下所示:

            public void paintValue(Graphics gfx,
                                Rectangle box)

paintValue方法会在给定区域中绘制值的表示形式,参数gfx是要绘制的Graphics对象;参数box是应该在其中绘制图形对象的矩形。

下面把第5.6节中创建的MyShape.java、ShapePropertyEditor.java和MyShapeBeanInfo.java文件复制到myshape1包中,并在ShapePropertyEditor中添加如下代码:

                public boolean isPaintable() {
                    return true;
                }
                public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
                    Color oldColor = gfx.getColor();
                    gfx.setColor(Color.black);
                    String shape = this.getAsText();
                    if (shape == "Oval") {
                      gfx.drawOval(box.x, box.y, box.width-3, box.height-3);
                    } else if (shape == "Round Rectangle") {
                      gfx.drawRoundRect(box.x, box.y, box.width-3, box.height-3, 20, 20);
                    }  else if (shape == "Arc") {
                      gfx.drawArc(box.x, box.y, box.width-3, box.height-3, 0, 180);
                    } else {
                      gfx.drawRect(box.x, box.y, box.width-3, box.height-3);
                    }
                    gfx.setColor(oldColor);
                }

当把MyShape添加到窗体上时,会显示如图5-29所示的界面。对应的属性编辑器的显示界面如图5-30所示。可以看到,根据当前的属性值,会在“属性”窗口中绘制相应的图形。

图5-29 绘制MyShape的shape属性

图5-30 自绘制的属性编辑器

5.8 自定义属性编辑器

有些时候,如果属性比较复杂,可以打开一个复杂、特殊的属性编辑器,进行属性的修改。本节将讨论如何设计自定义属性编辑器。

PropertyEditorSupport类的supportsCustomEditor方法用于确定此属性编辑器是否支持自定义编辑器。如果提供定义的属性编辑器,则需要在派生类中覆盖这个方法,并使得方法的返回值为true。

另外,还需要覆盖getCustomEditor方法,返回自定义的属性编辑器。getCustomEditor方法的语法定义如下所示:

            Component getCustomEditor()

通常会先创建一个可以编辑属性的窗体,然后在这个方法中创建这个窗体,并通过方法返回。

下面把第5.6节中创建的MyShape.java、ShapePropertyEditor.java和MyShapeBeanInfo.java文件复制到myshape2包中,并在ShapePropertyEditor中添加如下代码:

                public boolean supportsCustomEditor() {
                  return true;
                }
                public Component getCustomEditor() {
                  return new CustomShape(this);
                }

其中CustomShape为自定义的属性编辑器类。该类继承于JPanel类。在面板上添加如图5-31所示的组件,并编写如下代码。

图5-31 自定义的属性编辑器界面

            /*
             * CustomShape.java
             *
             */
            package myshape2;
            import java.beans.Customizer;
            import java.beans.PropertyChangeListener;
            import java.beans.PropertyChangeSupport;
            import java.util.Enumeration;
            import javax.swing.JRadioButton;
            /**
             *
             * @author  Liu Bin
             */
            public class CustomShape extends javax.swing.JPanel {
                private ShapePropertyEditor editor;
                public CustomShape(ShapePropertyEditor editor) {
                  this.editor = editor;
                  initComponents();
                  setSelectedStatus();
                }
                // <editor-fold defaultstate="collapsed" desc=" 生成的代码 ">
                private void initComponents() {
                  buttonGroup1 = new javax.swing.ButtonGroup();
                  jRadioButton1 = new javax.swing.JRadioButton();
                  jRadioButton2 = new javax.swing.JRadioButton();
                  jRadioButton3 = new javax.swing.JRadioButton();
                  jRadioButton4 = new javax.swing.JRadioButton();
                  buttonGroup1.add(jRadioButton1);
                  jRadioButton1.setSelected(true);
                  jRadioButton1.setText("Rectangle");
                  jRadioButton1.setBorder(javax.swing.BorderFactory.createEmptyBorder(0,0, 0, 0));
                  jRadioButton1.setMargin(new java.awt.Insets(0, 0, 0, 0));
                  jRadioButton1.addItemListener(new java.awt.event.ItemListener() {
                      public void itemStateChanged(java.awt.event.ItemEvent evt) {
                          jRadioButtonItemStateChanged(evt);
                      }
                  });
                  buttonGroup1.add(jRadioButton2);
                  jRadioButton2.setText("Arc");
                  jRadioButton2.setBorder(javax.swing.BorderFactory.createEmptyBorder(0,0, 0, 0));
                  jRadioButton2.setMargin(new java.awt.Insets(0, 0, 0, 0));
                  jRadioButton2.addItemListener(new java.awt.event.ItemListener() {
                      public void itemStateChanged(java.awt.event.ItemEvent evt) {
                          jRadioButtonItemStateChanged(evt);
                      }
                  });
                  buttonGroup1.add(jRadioButton3);
                  jRadioButton3.setText("Oval");
                  jRadioButton3.setBorder(javax.swing.BorderFactory.createEmptyBorder(0,0, 0, 0));
                  jRadioButton3.setMargin(new java.awt.Insets(0, 0, 0, 0));
                  jRadioButton3.addItemListener(new java.awt.event.ItemListener() {
                      public void itemStateChanged(java.awt.event.ItemEvent evt) {
                          jRadioButtonItemStateChanged(evt);
                      }
                  });
                  buttonGroup1.add(jRadioButton4);
                  jRadioButton4.setText("Round Rectangle");
                  jRadioButton4.setBorder(javax.swing.BorderFactory.createEmptyBorder(0,0, 0, 0));
                  jRadioButton4.setMargin(new java.awt.Insets(0, 0, 0, 0));
                  jRadioButton4.addItemListener(new java.awt.event.ItemListener() {
                      public void itemStateChanged(java.awt.event.ItemEvent evt) {
                          jRadioButtonItemStateChanged(evt);
                      }
                  });
                  org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
                  this.setLayout(layout);
                  layout.setHorizontalGroup(
        layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                      .add(layout.createSequentialGroup()
                      .addContainerGap()
                      .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(jRadioButton1)
                            .add(jRadioButton2)
                            .add(jRadioButton3)
                            .add(jRadioButton4))
                          .addContainerGap(72, Short.MAX_VALUE))
                  );
                  layout.setVerticalGroup(
                  layout.createParallelGroup(org.jdesktop.layout.GroupLayout. LEADING)
                      .add(layout.createSequentialGroup()
                          .addContainerGap()
                          .add(jRadioButton1)
                          .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                          .add(jRadioButton2)
                          .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                          .add(jRadioButton3)
                          .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                          .add(jRadioButton4)
                          .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,Short.MAX_VALUE))
                  );
                }// </editor-fold>
                private void jRadioButtonItemStateChanged(java.awt.event.ItemEvent evt) {
                  updateSelectedStatus();
                  editor.firePropertyChange();
                }
                // 变量声明 - 不进行修改
                private javax.swing.ButtonGroup buttonGroup1;
                private javax.swing.JRadioButton jRadioButton1;
                private javax.swing.JRadioButton jRadioButton2;
                  private javax.swing.JRadioButton jRadioButton3;
                  private javax.swing.JRadioButton jRadioButton4;
                  // 变量声明结束
                  public void setSelectedStatus() {
                      for (Enumeration e=buttonGroup1.getElements(); e.hasMoreElements(); ) {
                        JRadioButton rb = (JRadioButton)e.nextElement();
                        if (rb.getText().equalsIgnoreCase(editor.getAsText())) {
                            rb.setSelected(true);
                            break;
                        }
                      }
                  }
                  public void updateSelectedStatus() {
                      for (Enumeration e=buttonGroup1.getElements(); e.hasMoreElements(); ) {
                        JRadioButton rb = (JRadioButton)e.nextElement();
                        if (rb.isSelected()) {
                            editor.setValue(rb.getText());
                        }
                      }
                  }
              }

当把MyShape添加到窗体上,并在“属性”窗口中单击shape属性旁边的“…”按钮时,会打开如图5-32所示的属性编辑界面。

图5-32 自定义的属性编辑器

5.9 小结

JavaBean是Java技术中很重要的一个技术,通过JavaBean技术可以实现代码的复用。本章主要介绍了在NetBeans中快速开发JavaBean的各种方法,包括如何通过模板创建一个简单的JavaBean、如何创建JavaBean的属性、如何定制属性编辑器,以及如何生成BeanInfo等内容。

另外,值得一提的是,本章中还给出了通过连接模式实现事件连接的方法。通过连接模式可以快速产生代码,因此读者应该好好掌握这种方法。