58.java编程思想——创建窗口和程序片 一个复杂的Bean
这个程序是一张不论鼠标何时移动都围绕它画一个小圆的,并且一个动作接收器被激活。画布。当按下鼠标键时,我们可以改变的属性是圆的大小,除此之外还有被显示文字的色彩,大小,内容。BangBean同样拥有它自己的addActionListener()和removeActionListener()方法,因此我们可以附上自己的当用户单击在BangBean上时会被激活的接收器。
1 代码import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
public class BangBean extends Canvas implements Serializable {
protected intxm,ym;
protected intcSize= 20; // Circle size
protected String text= "Bang!";
protected intfontSize= 48;
protected Color tColor= Color.red;
protected ActionListener actionListener;
public BangBean() {
addMouseListener(new ML());
addMouseMotionListener(new MML());
}
public intgetCircleSize() {
return cSize;
}
public voidsetCircleSize(intnewSize){
cSize = newSize;
}
public String getBangText() {
return text;
}
public voidsetBangText(String newText){
text = newText;
}
public intgetFontSize() {
return fontSize;
}
public voidsetFontSize(intnewSize){
fontSize = newSize;
}
public Color getTextColor() {
return tColor;
}
public voidsetTextColor(Color newColor){
tColor = newColor;
}
public voidpaint(Graphics g){
g.setColor(Color.black);
g.drawOval(xm - cSize / 2, ym- cSize/ 2, cSize,cSize);
}
// This is a unicastlistener, which is
// thesimplest form of listener management:
public voidaddActionListener(ActionListener l) throwsTooManyListenersException {
if (actionListener != null)
throw new TooManyListenersException();
actionListener = l;
}
public voidremoveActionListener(ActionListener l) {
actionListener = null;
}
class ML extendsMouseAdapter {
public void mousePressed(MouseEvent e) {
Graphics g = getGraphics();
g.setColor(tColor);
g.setFont(new Font("TimesRoman", Font.BOLD, fontSize));
int width = g.getFontMetrics().stringWidth(text);
g.drawString(text, (getSize().width - width) / 2, getSize().height / 2);
g.dispose();
// Call the listener's method:
if (actionListener != null)
actionListener.actionPerformed(new ActionEvent(BangBean.this, ActionEvent.ACTION_PERFORMED,null));
}
}
class MML extendsMouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
// Testing theBangBean:
public staticvoidmain(String[] args){
BangBean bb = new BangBean();
try {
bb.addActionListener(new BBL());
} catch (TooManyListenersException e) {
}
Frame aFrame = new Frame("BangBean Test");
aFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(bb, BorderLayout.CENTER);
aFrame.setSize(300, 300);
aFrame.setVisible(true);
}
// Duringtesting, send action information
// to theconsole:
static classBBL implementsActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("BangBean action");
}
}
} /// :~
最重要的是我们会注意到BangBean 执行了这种串联化的接口。这意味着应用程序构建工具可以在程序设计者调整完属性值后利用串联为BangBean 贮藏所有的信息。当Bean 作为运行的应用程序的一部分被创建时,那些被贮藏的属性被重新恢复,因此我们可以正确地得到我们的设计。
我们能看到通常同Bean 一起运行的所有的字段都是专用的——允许只能通过方法来访问,通常利用“属性”结构。
当我们注视着addActionListener()的签名时,我们会注意到它可以产生出一个TooManyListenerException
(太多接收器异常)。这个异常指明它是一个单一的类型的,意味着当事件发生时,它只能通知一个接收器。一般情况下,我们会使用具有多种类型的事件,以便一个事件通知多个的接收器。但是,那样会陷入直到下一章我们才能准备好的结局中,因此这些内容会被重新回顾(下一个标题是“Java Beans 的重新回顾”)。单一类型的事件回避了这个难题。
当我们按下鼠标键时,文字被安入BangBean 中间,并且如果动作接收器字段存在,它的actionPerformed()方法就被调用,创建一个新的ActionEvent 对象在处理过程中。无论何时鼠标移动,它的新座标将被捕捉,并且画布会被重画(像我们所看到的抹去一些画布上的文字)。
main()方法增加了允许我们从命令行中测试程序的功能。当一个Bean 在一个开发环境中,main()方法不会被使用,但拥有它是绝对有益的,因为它提供了快捷的测试能力。无论何时一个ActionEvent 发生,main()方法都将创建了一个帧并安置了一个BangBean 在它里面,还在BangBean 中附上了一个简单的动作接收器以打印到控制台。当然,一般来说应用程序构建工具将创建大多数的Bean 的代码。当我们通过BeanDumper 或者安放BangBean 到一个可激活Bean 的开发环境中去运行BangBean 时,我们会注意到会有很多额外的属性和动作明显超过了上面的代码。那是因为BangBean从画布中继承,并且画布就是一个Bean,因此我们看到它的属性和事件同样的合适。
2 B e a n 的封装在我们可以安放一个Bean 到一个可激活Bean 的可视化构建工具中前,它必须被放入到标准的Bean 容器里,也就是包含Bean 类和一个表示“这是一个Bean”的清单文件的JAR(JavaARchive,Java 文件)文件中。
清单文件是一个简单的紧随事件结构的文本文件。对于BangBean 而言,清单文件就像下面这样:
Manifest-Version: 1.0
Name: bangbean/BangBean.class
Java-Bean: True
其中,第一行指出清单文件结构的版本,这是SUN 公司在很久以前公布的版本。第二行(空行忽略)对文件命名为BangBean.class。第三行表示“这个文件是一个Bean”。没有第三行,程序构建工具不会将类作为一个Bean 来认可。
唯一难以处理的部分是我们必须肯定“Name:”字段中的路径是正确的。如果我们回顾BangBean.java ,我们会看到它在package bangbean(因为存放类路径的子目录称为“bangbean”)中,并且这个名字在清单文件中必须包括封装的信息。另外,我们必须安放清单文件在我们封装路径的根目录上,在这个例子中意味着安放文件在bangbean 子目录中。这之后,我们必须从同一目录中调用Jar 来作为清单文件,如下所示:
jar cfm BangBean.jar BangBean.mf bangbean
这个例子假定我们想产生一个名为BangBean.jar 的文件并且我们将清单放到一个称为BangBean.mf 文件中。我们可能会想“当我编译BangBean.java 时,产生的其它类会怎么样呢?”哦,它们会在bangbean 子目录中被中止,并且我们会注意到上面jar 命令行的最后一个自变量就是bangbean 子目录。当我们给jar 子目录名时,它封装整个的子目录到jar 文件中(在这个例子中,包括BangBean.java 的源代码文件——对于我们自己的Bean 我们可能不会去选择包含源代码文件。)另外,如果我们改变主意,解开打包的JAR 文件,我们会发现我们清单文件并不在里面,但jar 创建了它自己的清单文件(部分根据我们的文件),称为MAINFEST.MF 并且安放它到META-INF 子目录中(代表“meta-information”)。如果我们打开这个清单文件,我们同样会注意到jar 为每个文件加入数字签名信息,其结构如下:
Digest-Algorithms: SHA MD5
SHA-Digest:pDpEAG9NaeCx8aFtqPI4udSX/O0=
MD5-Digest:O4NcS1hE3Smnzlp2hj6qeg==
一般来说,我们不必担心这些,如果我们要做一些修改,可以修改我们的原始的清单文件并且重新调用jar以为我们的Bean 创建了一个新的JAR文件。我们同样也可以简单地通过增加其它的Bean 的信息到我们清单文件来增加它们到JAR 文件中。
值得注意的是我们或许需要安放每个Bean 到它自己的子目录中,因为当我们创建一个JAR 文件时,分配JAR应用目录名并且JAR放置子目录中的任何文件到JAR 文件中。我们可以看到Frog 和BangBean 都在它们自己的子目录中。
一旦我们将我们的Bean 正确地放入一个JAR 文件中,我们就可以携带它到一个可以激活Bean 的编程环境中使用。使用这种方法,我们可以从一种工具到另一种工具间交替变换,但SUN 公司为Java Beans 提供了免费高效的测试工具在它们的“Bean Development Kit,Bean 开发工具”(BDK)称为“beanbox”。(我们可以从www.javasoft.com 处下载。)在我们启动beanbox 前,放置我们的Bean 到beanbox 中,复制JAR文件到BDK 的“jars”子目录中。
3 更复杂的B e a n 支持创建一个Bean 显然多么的简单。在程序设计中我们几乎不受到任何的限制。Java Bean 的设计提供了一个简单的输入点,这样可以提高到更复杂的层次上。这些高层次的问题超出了这本书所要讨论的范围,但它们会在此做简要的介绍。
我们增加更加复杂的程序和它的属性到一个位置。上面的例子显示一个独特的属性,当然它也可能代表一个数组的属性。这称为索引属性。我们简单地提供一个相应的方法(再者有一个方法名的命名规则)并且Introspector 认可索引属性,因此我们的应用程序构建工具相应的处理。
属性可以被捆绑,这意味着它们将通过PropertyChangeEvent通知其它的对象。其它的对象可以随后根据对Bean 的改变选择修改它们自己。
属性可以被束缚,这意味着其它的对象可以在一个属性的改变不能被接受时,拒绝它。其它的对象利用一个PropertyChangeEvent 来通知,并且它们产生一个ProptertyVetoException去阻止修改的发生,并恢复为原来的值。
同样能够改变我们的Bean 在设计时的被描绘成的方法:
(1) 我们可以为我们特殊的Bean 提供一个定制的属性表。这个普通的属性表将被所有的Bean 所使用,但当我们的Bean 被选择时,它会自动地调用这张属性表。
(2) 我们可以为一个特殊的属性创建一个定制的编辑器,因此普通的属性表被使用,但当我们指定的属性被调用时,编辑器会自动地被调用。
(3)我们可以为我们的Bean 提供一个定制的BeanInfo 类,产生的信息不同于由Introspector 默认产生的。
(4) 它同样可能在所有的FeatureDescriptors中改变“expert”的开关模式,以辨别基本特征和更复杂的特征。
4 B e a n 更多的知识另外有关的争议是Bean 不能被编址。无论何时我们创建一个Bean,都希望它会在一个多线程的环境中运行。这意味着我们必须理解线程的出口。