会员: 密码:  免费注册 | 忘记密码 | 会员登录 网页功能: 加入收藏 设为首页 网站搜索  
技术文档 > JAVA
Java编程思想(16)
发表日期:2003-06-16 00:00:00作者: 出处:  

第十三章 创建窗口和程序片(下)

13.17.3 剪贴板

Java 1.1对系统剪贴板提供有限的操作支持(在Java.awt.datatransfer package里)。我们可以将字符串作这文字对象复制到剪贴板中,并且我们可以从剪贴板中粘贴文字到字符中对角中。当然,剪贴板被设计来容纳各种类型的数据,存在于剪贴板上的数据通过程序运行剪切和粘贴进入到程序中。虽然剪切板目前只支持字符串数据,Java的剪切板API通过“特色”概念提供了良好的可扩展性。当数据从剪贴板中出来时,它拥有一个相关的特色集,这个特色集可以被修改(例如,一个图形可以被表示成一些字符串或者一幅图像)并且我们会注意到如果特殊的剪贴板数据支持这种特色,我们会对此十分的感兴趣。

下面的程序简单地对TextArea中的字符串数据进行剪切,复制,粘贴的操作做了示范。我们将注意到的是我们需要按照剪切、复制和粘贴的顺序进行工作。但如果我们看见一些其它程序中的TextField或者TextArea,我们会发现它们同样也自动地支持剪贴板的操作顺序。程序中简单地增加了剪贴板的程序化控制,如果我们想用它来捕捉剪贴板上的文字到一些非文字组件中就可以使用这种技术。

//: CutAndPaste.java

// Using the clipboard from Java 1.1

import java.awt.*;

import java.awt.event.*;

import java.awt.datatransfer.*;

public class CutAndPaste extends Frame {

 MenuBar mb = new MenuBar();

 Menu edit = new Menu("Edit");

 MenuItem

  cut = new MenuItem("Cut"),

  copy = new MenuItem("Copy"),

  paste = new MenuItem("Paste");

 TextArea text = new TextArea(20,20);

 Clipboard clipbd =

  getToolkit().getSystemClipboard();

 public CutAndPaste() {

  cut.addActionListener(new CutL());

  copy.addActionListener(new CopyL());

  paste.addActionListener(new PasteL());

  edit.add(cut);

  edit.add(copy);

  edit.add(paste);

  mb.add(edit);

  setMenuBar(mb);

  add(text, BorderLayout.CENTER);

 }

 class CopyL implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   String selection = text.getSelectedText();

   StringSelection clipString =

    new StringSelection(selection);

   clipbd.setContents(clipString, clipString);

  }

 }

 class CutL implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   String selection = text.getSelectedText();

   StringSelection clipString =

    new StringSelection(selection);

   clipbd.setContents(clipString, clipString);

   text.replaceRange("",

    text.getSelectionStart(),

    text.getSelectionEnd());

  }

 }

 class PasteL implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   Transferable clipData =

    clipbd.getContents(CutAndPaste.this);

   try {

    String clipString =

     (String)clipData.

      getTransferData(

       DataFlavor.stringFlavor);

    text.replaceRange(clipString,

     text.getSelectionStart(),

     text.getSelectionEnd());

   } catch(Exception ex) {

    System.out.println("not String flavor");

   }

  }

 }

 public static void main(String[] args) {

  CutAndPaste cp = new CutAndPaste();

  cp.addWindowListener(

   new WindowAdapter() {

    public void windowClosing(WindowEvent e) {

     System.exit(0);

    }

   });

  cp.setSize(300,200);

  cp.setVisible(true);

 }

} ///:~

创建和增加菜单及TextArea到如今似乎已变成一种单调的活动。这与通过工具组件创建的剪贴板字段clipbd有很大的区别。

所有的动作都安置在接收器中。CopyL和Cupl接收器同样除了最后的CutL线以外删除被复制的线。特殊的两条线是StringSelection对象从字符串从创建并调用StringSelection的setContents()方法。说得更准确些,就是放一个字符串到剪切板上。

在PasteL中,数据被剪贴板利用getContents()进行分解。任何返回的对象都是可移动的匿名的,并且我们并不真正地知道它里面包含了什么。有一种发现的方法是调用getTransferDateFlavors(),返回一个DataFlavor对象数组,表明特殊对象支持这种特点。我们同样能要求它通过我们感兴趣的特点直接地使用IsDataFlavorSupported()。但是在这里使用一种大胆的方法:调用getTransferData( )方法,假设里面的内容支持字符串特色,并且它不是个被分类在异常处理器中的难题 。

在将来,我们希望更多的数据特色能够被支持。

13.18 可视编程和Beans

迄今为止,我们已看到Java对创建可重复使用的代码片工作而言是多么的有价值。“最大限度地可重复使用”的代码单元拥有类,因为它包含一个紧密结合在一起的单元特性(字段)和单元动作(方法),它们可以直接经过混合或通过继承被重复使用。

继承和多形态性是面向对象编程的精华,但在大多数情况下当我们创建一个应用程序时,我们真正最想要的恰恰是我们最需要的组件。我们希望在我们的设计中设置这些部件就像电子工程师在电路板上创造集成电路块一样(在使用Java的情况下,就是放到WEB页面上)。这似乎会成为加快这种“模块集合”编制程序方法的发展。

“可视化编程”最早的成功——非常的成功——要归功于微软公司的Visual Basic(VB,可视化Basic语言),接下来的第二代是Borland公司Delphi(一种客户/服务器数据库应用程序开发工具,也是Java Beans设计的主要灵感)。这些编程工具的组件的像征就是可视化,这是不容置疑的,因为它们通常展示一些类型的可视化组件,例如:一个按惯或一个TextField。事实上,可视化通常表现为组件可以非常精确地访问运行中程序。因此可视化编程方法的一部分包含从一个调色盘从拖放一个组件并将它放置到我们的窗体中。应用程序创建工具像我们所做的一样编写程序代码,该代码将导致正在运行的程序中的组件被创建。

简单地拖放组件到一个窗体中通常不足以构成一个完整的程序。一般情况下,我们需要改变组件的特性,例如组件的色彩,组件的文字,组件连结的数据库,等等。特性可以参照属性在编程时进行修改。我们可以在应用程序构建工具中巧妙处置我们组件的属性,并且当我们创建程序时,构建数据被保存下来,所以当该程序被启动时,数据能被重新恢复。

到如今,我们可能习惯于使用对象的多个特性,这也是一个动作集合。在设计时,可视化组件的动作可由事件部分地代表,意味着“任何事件都可以发生在组件上”。通常,由我们决定想发生的事件,当一个事件发生时,对所发生的事件连接代码。

这是关键性的部分:应用程序构建工具可以动态地询问组件(利用映象)以发现组件支持的事件和属件。一旦它知道它们的状态,应用程序构建工具就可以显示组件的属性并允许我们修改它们的属性(当我们构建程序时,保存它们的状态),并且也显示这些事件。一般而言,我们做一些事件像双击一个事件以及应用程序构建工具创建一个代码并连接到事件上。当事件发生时,我们不得不编写执行代码。应用程序构建工具累计为我们做了大量的工作。结果我们可以注意到程序看起来像它所假定的那样运行,并且依赖应用程序构建工具去为我们管理连接的详细资料。可视化的编程工具如此成功的原因是它们明显加快构建的应用程序的处理过程——当然,用户接口作为应用程序的一部分同样的好。

13.18.1 什么是Bean

在经细节处理后,一个组件在类中被独特的具体化,真正地成为一块代码。关键的争议在于应用程序构建工具发现组件的属性和事件能力。为了创建一个VB组件,程序开发者不得不编写正确的同时也是复杂烦琐的代码片,接下来由某些协议去展现它们的事件和属性。Delphi是第二代的可视化编程工具并且这种开发语言主动地围绕可视化编程来设计因此它更容易去创建一个可视化组件。但是,Java带来了可视化的创作组件做为Java Beans最高级的“装备”,因为一个Bean就是一个类。我们不必再为制造任何的Bean而编写一些特殊的代码或者使用特殊的编程语言。事实上,我们唯一需要做的是略微地修改我们对我们方法命名的办法。方法名通知应用程序构建工具是否是一个属性,一个事件或是一个普通的方法。

在Java的文件中,命名规则被错误地曲解为“设计范式”。这十分的不幸,因为设计范式(参见第16章)惹来不少的麻烦。命名规则不是设计范式,它是相当的简单:

(1) 因为属性被命名为xxx,我们代表性的创建两个方法:getXxx()和setXxx()。注意get或set后的第一个字母小写以产生属性名。“get”和“set”方法产生同样类型的自变量。“set”和“get”的属性名和类型名之间没有关系。

(2) 对于布尔逻辑型属性,我们可以使用上面的“get”和“set”方法,但我们也可以用“is”代替“ get”。

(3) Bean的普通方法不适合上面的命名规则,但它们是公用的。

4.对于事件,我们使用“listener(接收器)”方法。这种方法完全同我们看到过的方法相同:(addFooBarListener(FooBarListener)和removeFooBarListener(FooBarListener)方法用来处理FooBar事件。大多数时候内建的事件和接收器会满足我们的需要,但我们可以创建自己的事件和接收器接口。

上面的第一点回答了一个关于我们可能注意到的从Java 1.0到Java 1.1的改变的问题:一些方法的名字太过于短小,显然改写名字毫无意义。现在我们可以看到为了制造Bean中的特殊的组件,大多数的这些修改不得不适合于“get”和“set”命名规则。

现在,我们已经可以利用上面的这些指导方针去创建一个简单的Bean:

//: Frog.java

// A trivial Java Bean

package frogbean;

import java.awt.*;

import java.awt.event.*;

class Spots {}

public class Frog {

 private int jumps;

 private Color color;

 private Spots spots;

 private boolean jmpr;

 public int getJumps() { return jumps; }

 public void setJumps(int newJumps) {

  jumps = newJumps;

 }

 public Color getColor() { return color; }

 public void setColor(Color newColor) {

  color = newColor;

 }

 public Spots getSpots() { return spots; }

 public void setSpots(Spots newSpots) {

  spots = newSpots;

 }

 public boolean isJumper() { return jmpr; }

 public void setJumper(boolean j) { jmpr = j; }

 public void addActionListener(

   ActionListener l) {

  //...

 }

 public void removeActionListener(

   ActionListener l) {

  // ...

 }

 public void addKeyListener(KeyListener l) {

  // ...

 }

 public void removeKeyListener(KeyListener l) {

  // ...

 }

 // An "ordinary" public method:

 public void croak() {

  System.out.println("Ribbet!");

 }

} ///:~

首先,我们可看到Bean就是一个类。通常,所有我们的字段会被作为专用,并且可以接近的唯一办法是通过方法。紧接着的是命名规则,属性是jump,color,jumper,spots(注意这些修改是在第一个字母在属性名的情况下进行的)。虽然内部确定的名字同最早的三个例子的属性名一样,在jumper中我们可以看到属性名不会强迫我们使用任何特殊的内部可变的名字(或者,真的拥有一些内部的可变的属性名)。

Bean事件的句柄是ActionEvent和KeyEvent,这是根据有关接收器的“add”和“remove”命名方法得出的。最后我们可以注意到普通的方法croak()一直是Bean的一部分,仅仅是因为它是一个公共的方法,而不是因为它符合一些命名规则。

13.18.2 用Introspector提取BeanInfo

当我们拖放一个Bean的调色板并将它放入到窗体中时,一个Bean的最关键的部分的规则发生了。应用程序构建工具必须可以创建Bean(如果它是默认的构建器的话,它就可以做)然后,在此范围外访问Bean的源代码,提取所有的必要的信息以创立属性表和事件处理器。

解决方案的一部分在11章结尾部分已经显现出来:Java 1.1版的映象允许一个匿名类的所有方法被发现。这完美地解决了Bean的难题而无需我们使用一些特殊的语言关键字像在其它的可视化编程语言中所需要的那样。事实上,一个主要的原因是映象增加到Java 1.1版中以支持Beans(尽管映象同样支持对象串联和远程方法调用)。因为我们可能希望应用程序构建工具的开发者将不得不映象每个Bean并且通过它们的方法搜索以找到Bean的属性和事件。

这当然是可能的,但是Java的研制者们希望为每个使用它的用户提供一个标准的接口,而不仅仅是使Bean更为简单易用,不过他们也同样提供了一个创建更复杂的Bean的标准方法。这个接口就是Introspector类,在这个类中最重要的方法静态的getBeanInfo()。我们通过一个类处理这个方法并且getBeanInfo()方法全面地对类进行查询,返回一个我们可以进行详细研究以发现其属性、方法和事件的BeanInfo对象。

通常我们不会留意这样的一些事物——我们可能会使用我们大多数的现成的Bean,并且我们不需要了解所有的在底层运行的技术细节。我们会简单地拖放我们的Bean到我们窗体中,然后配置它们的属性并且为事件编写处理器。无论如何它都是一个有趣的并且是有教育意义的使用Introspector来显示关于Bean信息的练习,好啦,闲话少说,这里有一个工具请运行它(我们可以在forgbean子目录中找到它):

//: BeanDumper.java

// A method to introspect a Bean

import java.beans.*;

import java.lang.reflect.*;

public class BeanDumper {

 public static void dump(Class bean){

  BeanInfo bi = null;

  try {

   bi = Introspector.getBeanInfo(

    bean, java.lang.Object.class);

  } catch(IntrospectionException ex) {

   System.out.println("Couldn't introspect " +

    bean.getName());

   System.exit(1);

  }

  PropertyDescriptor[] properties =

   bi.getPropertyDescriptors();

  for(int i = 0; i < properties.length; i++) {

   Class p = properties[i].getPropertyType();

   System.out.println(

    "Property type:\n " + p.getName());

   System.out.println(

    "Property name:\n " +

    properties[i].getName());

   Method readMethod =

    properties[i].getReadMethod();

   if(readMethod != null)

    System.out.println(

     "Read method:\n " +

     readMethod.toString());

   Method writeMethod =

    properties[i].getWriteMethod();

   if(writeMethod != null)

    System.out.println(

     "Write method:\n " +

     writeMethod.toString());

   System.out.println("====================");

  }

  System.out.println("Public methods:");

  MethodDescriptor[] methods =

   bi.getMethodDescriptors();

  for(int i = 0; i < methods.length; i++)

   System.out.println(

    methods[i].getMethod().toString());

  System.out.println("======================");

  System.out.println("Event support:");

  EventSetDescriptor[] events =

   bi.getEventSetDescriptors();

  for(int i = 0; i < events.length; i++) {

   System.out.println("Listener type:\n " +

    events[i].getListenerType().getName());

   Method[] lm =

    events[i].getListenerMethods();

   for(int j = 0; j < lm.length; j++)

    System.out.println(

     "Listener method:\n " +

     lm[j].getName());

   MethodDescriptor[] lmd =

    events[i].getListenerMethodDescriptors();

   for(int j = 0; j < lmd.length; j++)

    System.out.println(

     "Method descriptor:\n " +

     lmd[j].getMethod().toString());

   Method addListener =

    events[i].getAddListenerMethod();

   System.out.println(

     "Add Listener Method:\n " +

    addListener.toString());

   Method removeListener =

    events[i].getRemoveListenerMethod();

   System.out.println(

    "Remove Listener Method:\n " +

    removeListener.toString());

   System.out.println("====================");

  }

 }

 // Dump the class of your choice:

 public static void main(String[] args) {

  if(args.length < 1) {

   System.err.println("usage: \n" +

    "BeanDumper fully.qualified.class");

   System.exit(0);

  }

  Class c = null;

  try {

   c = Class.forName(args[0]);

  } catch(ClassNotFoundException ex) {

   System.err.println(

    "Couldn't find " + args[0]);

   System.exit(0);

  }

  dump(c);

 }

} ///:~

BeanDumper.dump()是一个可以做任何工作的方法。首先它试图创建一个BeanInfo对象,如果成功地调用BeanInfo的方法,就产生关于属性、方法和事件的信息。在Introspector.getBeanInfo()中,我们会注意到有一个另外的自变量。由它来通知Introspector访问继承体系的地点。在这种情况下,它在分析所有对象方法前停下,因为我们对看到那些并不感兴趣。

因为属性,getPropertyDescriptors()返回一组的属性描述符号。对于每个描述符号我们可以调用getPropertyType()方法彻底的通过属性方法发现类的对象。这时,我们可以用getName()方法得到每个属性的假名(从方法名中提取),getname()方法用getReadMethod()和getWriteMethod()完成读和写的操作。最后的两个方法返回一个可以真正地用来调用在对象上调用相应的方法方法对象(这是映象的一部分)。对于公共方法(包括属性方法),getMethodDescriptors( )返回一组方法描述字符。每一个我们都可以得到相当的方法对象并可以显示出它们的名字。

对于事件而言,getEventSetDescriptors()返回一组事件描述字符。它们中的每一个都可以被查询以找出接收器的类,接收器类的方法以及增加和删除接收器的方法。BeanDumper程序打印出所有的这些信息。

如果我们调用BeanDumper在Frog类中,就像这样:

java BeanDumper frogbean.Frog

它的输出结果如下(已删除这儿不需要的额外细节):

class name: Frog

Property type:

 Color

Property name:

 color

Read method:

 public Color getColor()

Write method:

 public void setColor(Color)

====================

Property type:

 Spots

Property name:

 spots

Read method:

 public Spots getSpots()

Write method:

 public void setSpots(Spots)

====================

Property type:

 boolean

Property name:

 jumper

Read method:

 public boolean isJumper()

Write method:

 public void setJumper(boolean)

====================

Property type:

 int

Property name:

 jumps

Read method:

 public int getJumps()

Write method:

 public void setJumps(int)

====================

Public methods:

public void setJumps(int)

public void croak()

public void removeActionListener(ActionListener)

public void addActionListener(ActionListener)

public int getJumps()

public void setColor(Color)

public void setSpots(Spots)

public void setJumper(boolean)

public boolean isJumper()

public void addKeyListener(KeyListener)

public Color getColor()

public void removeKeyListener(KeyListener)

public Spots getSpots()

======================

Event support:

Listener type:

 KeyListener

Listener method:

 keyTyped

Listener method:

 keyPressed

Listener method:

 keyReleased

Method descriptor:

 public void keyTyped(KeyEvent)

Method descriptor:

 public void keyPressed(KeyEvent)

Method descriptor:

 public void keyReleased(KeyEvent)

Add Listener Method:

 public void addKeyListener(KeyListener)

Remove Listener Method:

 public void removeKeyListener(KeyListener)

====================

Listener type:

 ActionListener

Listener method:

 actionPerformed

Method descriptor:

 public void actionPerformed(ActionEvent)

Add Listener Method:

 public void addActionListener(ActionListener)

Remove Listener Method:

 public void removeActionListener(ActionListener)

====================

这个结果揭示出了Introspector在从我们的Bean产生一个BeanInfo对象时看到的大部分内容。我们可注意到属性的类型和它们的名字是相互独立的。请注意小写的属性名。(当属性名开头在一行中有超过不止的大写字母,这一次程序就不会被执行。)并且请记住我们在这里所见到的方法名(例如读和与方法)真正地从一个可以被用来在对象中调用相关方法的方法对象中产生。

通用方法列表包含了不相关的事件或者属性,例如croak()。列表中所有的方法都是我们可以有计划的为Bean调用,并且应用程序构建工具可以选择列出所有的方法,当我们调用方法时,减轻我们的任务。

最后,我们可以看到事件在接收器中完全地分析研究它的方法、增加和减少接收器的方法。基本上,一旦我们拥有BeanInfo,我们就可以找出对Bean来说任何重要的事物。我们同样可以为Bean调用方法,即使我们除了对象外没有任何其它的信息(此外,这也是映象的特点)。

13.18.3 一个更复杂的Bean

接下的程序例子稍微复杂一些,尽管这没有什么价值。这个程序是一张不论鼠标何时移动都围绕它画一个小圆的 弧5蔽颐前聪率蟊昙?保?谄聊恢醒胂允疽桓鲎帧奥ang!”,并且一个动作接收器被激活。画布。当按下鼠标键时,我们可以改变的属性是圆的大小,除此之外还有被显示文字的色彩,大小,内容。BangBean同样拥有它自己的addActionListener()和removeActionListener()方法,因此我们可以附上自己的当用户单击在BangBean上时会被激活的接收器。这样,我们将能够确认可支持的属性和事件:

//: BangBean.java

// A graphical Bean

package bangbean;

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.util.*;

public class BangBean extends Canvas

   implements Serializable {

 protected int xm, ym;

 protected int cSize = 20; // Circle size

 protected String text = "Bang!";

 protected int fontSize = 48;

 protected Color tColor = Color.red;

 protected ActionListener actionListener;

 public BangBean() {

  addMouseListener(new ML());

  addMouseMotionListener(new MML());

 }

 public int getCircleSize() { return cSize; }

 public void setCircleSize(int newSize) {

  cSize = newSize;

 }

 public String getBangText() { return text; }

 public void setBangText(String newText) {

  text = newText;

 }

 public int getFontSize() { return fontSize; }

 public void setFontSize(int newSize) {

  fontSize = newSize;

 }

 public Color getTextColor() { return tColor; }

 public void setTextColor(Color newColor) {

  tColor = newColor;

 }

 public void paint(Graphics g) {

  g.setColor(Color.black);

  g.drawOval(xm - cSize/2, ym - cSize/2,

   cSize, cSize);

 }

 // This is a unicast listener, which is

 // the simplest form of listener management:

 public void addActionListener (

   ActionListener l)

    throws TooManyListenersException {

  if(actionListener != null)

   throw new TooManyListenersException();

  actionListener = l;

 }

 public void removeActionListener(

   ActionListener l) {

  actionListener = null;

 }

 class ML extends MouseAdapter {

  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 extends MouseMotionAdapter {

  public void mouseMoved(MouseEvent e) {

   xm = e.getX();

   ym = e.getY();

   repaint();

  }

 }

 public Dimension getPreferredSize() {

  return new Dimension(200, 200);

 }

 // Testing the BangBean:

 public static void main(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);

 }

 // During testing, send action information

 // to the console:

 static class BBL implements ActionListener {

  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,因此我们看到它的属性和事件同样的合适。

13.18.4 Bean的封装

在我们可以安放一个Bean到一个可激活Bean的可视化构建工具中前,它必须被放入到标准的Bean容器里,也就是包含Bean类和一个表示“这是一个Bean”的清单文件的JAR(Java ARchive,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”子目录中。

13.18.5 更复杂的Bean支持

我们可以看到创建一个Bean显然多么的简单。在程序设计中我们几乎不受到任何的限制。Java Bean的设计提供了一个简单的输入点,这样可以提高到更复杂的层次上。这些高层次的问题超出了这本书所要讨论的范围,但它们会在此做简要的介绍。我们可以在http://java.sun.com/beans上找到更多的详细资料。

我们增加更加复杂的程序和它的属性到一个位置。上面的例子显示一个独特的属性,当然它也可能代表一个数组的属性。这称为索引属性。我们简单地提供一个相应的方法(再者有一个方法名的命名规则)并且Introspector认可索引属性,因此我们的应用程序构建工具相应的处理。

属性可以被捆绑,这意味着它们将通过PropertyChangeEvent通知其它的对象。其它的对象可以随后根据对Bean的改变选择修改它们自己。

属性可以被束缚,这意味着其它的对象可以在一个属性的改变不能被接受时,拒绝它。其它的对象利用一个PropertyChangeEvent来通知,并且它们产生一个ProptertyVetoException去阻止修改的发生,并恢复为原来的值。

我们同样能够改变我们的Bean在设计时的被描绘成的方法:

(1) 我们可以为我们特殊的Bean提供一个定制的属性表。这个普通的属性表将被所有的Bean所使用,但当我们的Bean被选择时,它会自动地调用这张属性表。

(2) 我们可以为一个特殊的属性创建一个定制的编辑器,因此普通的属性表被使用,但当我们指定的属性被调用时,编辑器会自动地被调用。

(3)我们可以为我们的Bean提供一个定制的BeanInfo类,产生的信息不同于由Introspector默认产生的。

(4) 它同样可能在所有的FeatureDescriptors中改变“expert”的开关模式,以辨别基本特征和更复杂的特征。

13.18.6 Bean更多的知识

另外有关的争议是Bean不能被编址。无论何时我们创建一个Bean,都希望它会在一个多线程的环境中运行。这意味着我们必须理解线程的出口,我们将在下一章中介绍。我们会发现有一段称为“Java Beans的回顾”的节会注意到这个问题和它的解决方案。

13.19 Swing入门(注释⑦)

通过这一章的学习,当我们的工作方法在AWT中发生了巨大的改变后(如果可以回忆起很久以前,当Java第一次面世时SUN公司曾声明Java是一种“稳定,牢固”的编程语言),可能一直有Java还不十分的成熟的感觉。的确,现在Java拥有一个不错的事件模型以及一个优秀的组件复用设计——JavaBeans。但GUI组件看起来还相当的原始,笨拙以及相当的抽象。

⑦:写作本节时,Swing库显然已被Sun“固定”下来了,所以只要你下载并安装了Swing库,就应该能正确地编译和运行这里的代码,不会出现任何问题(应该能编译Sun配套提供的演示程序,以检测安装是否正确)。若遇到任何麻烦,请访问http://www.BruceEckel.com,了解最近的更新情况。

而这就是Swing将要占领的领域。Swing库在Java 1.1之后面世,因此我们可以自然而然地假设它是Java 1.2的一部分。可是,它是设计为作为一个补充在Java 1.1版中工作的。这样,我们就不必为了享用好的UI组件库而等待我们的平台去支持Java 1.2版了。如果Swing库不是我们的用户的Java 1.1版所支持的一部分,并且产生一些意外,那他就可能真正的需要去下载Swing库了。

Swing包含所有我们缺乏的组件,在整个本章余下的部分中:我们期望领会现代化的UI,来自按钮的任何事件包括到树状和网格结构中的图片。它是一个大库,但在某些方面它为任务被设计得相应的复杂——如果任何事都是简单的,我们不必编写更多的代码但同样设法运行我们的代码逐渐地变得更加的复杂。这意味着一个容易的入口,如果我们需要它我们得到它的强大力量。

Swing相当的深奥,这一节不会去试图让读者理解,但会介绍它的能力和Swing简单地使我们着手使用库。请注意我们有意识的使用这一切变得简单。如果我们需要运行更多的,这时Swing能或许能给我们所想要的,如果我们愿意深入地研究,可以从SUN公司的在线文档中获取更多的资料。

13.19.1 Swing有哪些优点

当我们开始使用Swing库时,会注意到它在技术上向前迈出了巨大的一步。Swing组件是Bean,因此他们可以支持Bean的任何开发环境中使用。Swing提供了一个完全的UI组件集合。因为速度的关系,所有的组件都很小巧的(没有“重量级”组件被使用),Swing为了轻便在Java中整个被编写。

最重要的是我们会希望Swing被称为“正交使用”;一旦我们采用了这种关于库的普遍的办法我们就可以在任何地方应用它们。这主要是因为Bean的命名规则,大多数的时候在我编写这些程序例子时我可以猜到方法名并且第一次就将它拼写正确而无需查找任何事物。这无疑是优秀库设计的品质证明。另外,我们可以广泛地插入组件到其它的组件中并且事件会正常地工作。

键盘操作是自动被支持的——我们可以使用Swing应用程序而不需要鼠标,但我们不得不做一些额外的编程工作(老的AWT中需要一些可怕的代码以支持键盘操作)。滚动被毫不费力地支持——我们简单地将我们的组件到一个JScrollPane中,同样我们再增加它到我们的窗体中即可。其它的特征,例如工具提示条只需要一行单独的代码就可执行。

Swing同样支持一些被称为“可插入外观和效果”的事物,这就是说UI的外观可以在不同的平台和不同的操作系统上被动态地改变以符合用户的期望。它甚至可以创造我们自己的外观和效果。

13.19.2 方便的转换

如果我们长期艰苦不懈地利用Java 1.1版构建我们的UI,我们并不需要扔掉它改变到Swing阵营中来。幸运的是,库被设计得允许容易地修改——在很多情况下我们可以简单地放一个“J”到我们老AWT组件的每个类名前面即可。下面这个例子拥有我们所熟悉的特色:

//: JButtonDemo.java

// Looks like Java 1.1 but with J's added

package c13.swing;

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

import javax.swing.*;

public class JButtonDemo extends Applet {

 JButton

  b1 = new JButton("JButton 1"),

  b2 = new JButton("JButton 2");

 JTextField t = new JTextField(20);

 public void init() {

  ActionListener al = new ActionListener() {

   public void actionPerformed(ActionEvent e){

    String name =

     ((JButton)e.getSource()).getText();

    t.setText(name + " Pressed");

   }

  };

  b1.addActionListener(al);

  add(b1);

  b2.addActionListener(al);

  add(b2);

  add(t);

 }

 public static void main(String args[]) {

  JButtonDemo applet = new JButtonDemo();

  JFrame frame = new JFrame("TextAreaNew");

  frame.addWindowListener(new WindowAdapter() {

   public void windowClosing(WindowEvent e){

    System.exit(0);

   }

  });

  frame.getContentPane().add(

   applet, BorderLayout.CENTER);

  frame.setSize(300,100);

  applet.init();

关于我们 / 给我留言 / 版权举报 / 意见建议 / 网站编程QQ群   
Copyright ©2003- 2024 Lihuasoft.net webmaster(at)lihuasoft.net 加载时间 0.00452