《JavaTM 程序设计语言 基础》第 2 部分
第 2 课:再谈用户界面

[<<后退] [目录] [下一课>>]

《JavaTM 程序设计语言基础 - 第 1 部分》中,您已经掌握了如何使用 Java 基本类 (JFC)的Project Swing (Project Swing) 组件来创建具有基本功能的简单用户 界面。也掌握了如何使用远程方法调用(RMI)应用编程接口(API)把来自客户 机程序的数据发送给网络上的服务器程序,以供其它客户机程序访问。

本课采用 第 1 部分、第 8 课:远程方 法调用中的 RMI 应用程序创建一个更加复杂的用户界面,并使用一个不同的 布局管理器。这些改变为您提供了由两类客户机程序组成的非常简单的电子商务 应用的入门知识:第一个客户机程序让终端用户填交采购订单,第二个客户机程 序使用订单处理器查看订单。


关于示例程序的说明

这是一个仅用来作为指导且非常简单的电子商务示例程序。它由三个程序组成: 两个客户程序(一个用于订购水果,另一个用于查看订单)和一个可供客户查看订 单信息的服务器程序。

水果订购客户程序

FruitClient程序产生一个用户界面,用于提示终端用户订购单价为 1.25 美 元的苹果、桃和梨。

用户把各种水果的订购数量输入到订单中后,按下回车键就可以提交该订单并更 新累计值。

可利用 Tab 键或鼠标把光标移动到下一个文本域中。在界面底部,终端用户 可提供信用卡卡号和客户 ID 号。

终端用户点击 Purchase(购买)按钮后,输入该表中的所有信 息就传输给服务器程序。

终端用户必需按下回车键才能更新累计值。如果没有按下回车键,通过网络传 输的订单累计值就不正确。本课最后要求您对程序进行修改,以便使终端用户即 使没有按下回车键,也不会有传输错误累计值的危险。

服务器程序

RemoteServer程序提供可远程访问的 sendget 方法。水果订购客户程序调用 send 方法向服务器发送数据,而浏 览订单的客户程序则调用 get 方法检索数据。本例中,服务器程 序没有用户界面。

查看订单的客户程序

OrderClient程序将创建一个用户界面,在终端用户点击 View Order (查看订单)按钮时,程序就会从服务器程序中提取订单并把订单显示在 屏幕上。

编译和运行示例程序

第 1 部分、第 8 课:远程方法调用 中已介绍了如何运行示例程序。本课采用第 1 部分第 8 课中的说明,但采用本课 提供的源代码。以下是对这些运行步骤的综合说明:

编译: 以下这些指令假设开发工作是在 zelda 主目录下进行的。

Unix:
cd /home/zelda/classes
javac Send.java
javac RemoteServer.java
javac RMIClient2.java
javac RMIClient1.java
rmic -d . RemoteServer
cp RemoteServer*.class /home/zelda/public_html/classes
cp Send.class /home/zelda/public_html/classes
Win32:
cd \home\zelda\classes
javac Send.java
javac RemoteServer.java
javac RMIClient2.java
javac RMIClient1.java
rmic -d . RemoteServer
copy RemoteServer*.class \home\zelda\public_html\classes
copy Send.class \home\zelda\public_html\classes

Start rmi Registry:

Unix:
cd /home/zelda/public_html/classes
unsetenv CLASSPATH
rmiregistry &
Win32:
cd \home\zelda\public_html\classes
set CLASSPATH=
start rmiregistry 

Start Remote Server:

Unix:
cd /home/zelda/public_html/classes
java 
-Djava.rmi.server.codebase=http://kq6py/~zelda/classes
-Djava.rmi.server.hostname=kq6py.eng.sun.com
-Djava.security.policy=java.policy RemoteServer
Win32:
cd \home\zelda\public_html\classes
java -Djava.rmi.server.codebase=
  file:c:\home\zelda\public_html\classes
-Djava.rmi.server.hostname=kq6py.eng.sun.com
-Djava.security.policy=java.policy RemoteServer

Start RMIClient1:

Unix:
cd /home/zelda/classes

java -Djava.rmi.server.codebase=
  http://kq6py/~zelda/classes/
-Djava.security.policy=java.policy RMIClient1 
  kq6py.eng.sun.com/~zelda
Win32:
cd \home\zeldzeldaa\classes

java -Djava.rmi.server.codebase=
  file:c:\home\zelda\classes\
-Djava.security.policy=java.policy RMIClient1 
kq6py.eng.sun.com\home\zelda\public\html

Start RMIClient2:

Unix:
cd /home/zelda/classes
java -Djava.rmi.server.codebase=
  http://kq6py/~zelda/classes
-Djava.rmi.server.hostname=kq6py.eng.sun.com
-Djava.security.policy=java.policy RMIClient2 
  kq6py.eng.sun.com
Win32:
cd \home\zelda\classes
java -Djava.rmi.server.codebase=
  file:c:\home\zelda\public_html\classes
-Djava.rmi.server.hostname=kq6py.eng.sun.com
-Djava.security.policy=java.policy RMIClient2 
  kq6py.eng.sun.com

水果订购客户程序

RMIClient1.java程序利用标签、文本域、文本区和按钮等组件创建订购水果 的用户界面。

在显示器上,用户界面的组件按两栏布置,左栏为标签、右栏为输入和输出数据域 (文本域和文本区)。

终端用户在文本域中输入自己的水果订购数量,输入每一种水果的数量后都应 按下回车键。按下回车键后文本域操作就会更新文本区中显示的总数量和总费用。

Reset 按钮的作用是清屏并重置总费用和总数量变量。 Purchase 按钮的功能是把订单数据发送给服务器程序。如果先点 击 Reset 按钮,然后再点击 Purchase 按钮,那么 就会向网络传送 null(空)值。

实例变量

以下几行程序声明了 SwingUI 类所使用的 Project Swing 组件 类。这些组件类都是实例变量,可被已经实例化的类中的任何方法所访问。本例中 ,这些类在 SwingUI 构造函数中创建并在 actionPerformed 方法实现时被访问。

  JLabel col1, col2; 
  JLabel totalItems, totalCost; 
  JLabel cardNum, custID;
  JLabel applechk, pearchk, peachchk;

  JButton purchase, reset;
  JPanel panel;

  JTextField appleqnt, pearqnt, peachqnt;
  JTextField creditCard, customer;
  JTextArea items, cost;

  static Send send;
  int itotal=0; 
  double icost=0;

构造函数

构造函数特别长,这是因为它要创建所有程序组件、设置界面中两栏表格的布 局、把所有组件放置在面板上的表格中。面板是容纳其它组件的容器。

ResetPurchase 按钮以及 appleQntpearQntpeachQnt 等文本域都是作为动作监视程 序添加的。这就是说,当终端用户点击其中的某个按钮或在任何一个文本域中按下 回车键时,就会发生一个动作事件,导致平台调用定义了这些组件特性的 FruitClient.actionPerformed 方法。

正如在 第 1 部分、第 4 课:创建用 户界面中所解释的那样,类用于对 ActionListener 接口进行 声明,并且在需要处理按钮点击及回车确认文本域等动作事件时实现 actionPerformed 方法。其它用户界面组件则产生不同的动作事件, 但要求用户运行不同的接口和方法。

//Create left and right column labels
col1 = new JLabel("Select Items");
col2 = new JLabel("Specify Quantity");

//Create labels and text field components
applechk = new JLabel("   Apples");
appleqnt = new JTextField();
appleqnt.addActionListener(this);

pearchk = new JLabel("   Pears");
pearqnt = new JTextField();
pearqnt.addActionListener(this);

peachchk = new JLabel("   Peaches");
peachqnt = new JTextField();
peachqnt.addActionListener(this);

cardNum = new JLabel("   Credit Card:");
creditCard = new JTextField();

customer = new JTextField();
custID = new JLabel("   Customer ID:");

//Create labels and text area components
totalItems = new JLabel("Total Items:");
totalCost = new JLabel("Total Cost:");
items = new JTextArea();
cost = new JTextArea();

//Create buttons and make action listeners
purchase = new JButton("Purchase");
purchase.addActionListener(this);

reset = new JButton("Reset");
reset.addActionListener(this);
以下程序行中,将创建一个 JPanel 组件并把该组件添加到顶级窗 体中,而且指定了布局管理器(layout manager)和背景颜色。布局管理器确定 用户界面组件在面板上的布置。

第 1 部分、第 4 课:创建用户界面 中的示例程序采用了 BorderLayout 布局管理器。本例中则使 用 GridLayout 布局管理器,它把用户界面的组件安排在您所指定 的表格或行或列中。示例程序使用了一个包含两列而不限制行数的表格,行和列数 量分别由语句 panel.setLayout(new GridLayout(0,2)); 中的 0(不限制行数)和 2 (两列)表示。

布局管理器和颜色在面板中设置,通过调用 JFrame 类的 getContentPane 方法来把该面板添加到内容窗格中。内容窗格让 不同类型的组件在 Project Swing 中一同起作用。

//Create a panel for the components
  panel = new JPanel();

//Set panel layout to 2-column grid 
//on a white background
  panel.setLayout(new GridLayout(0,2));
  panel.setBackground(Color.white);

//Add components to panel columns
//going left to right and top to bottom
  getContentPane().add(panel);
  panel.add(col1);
  panel.add(col2);

  panel.add(applechk);
  panel.add(appleqnt);

  panel.add(peachchk);
  panel.add(peachqnt);

  panel.add(pearchk);
  panel.add(pearqnt);

  panel.add(totalItems);
  panel.add(items);

  panel.add(totalCost);
  panel.add(cost);

  panel.add(cardNum);
  panel.add(creditCard);

  panel.add(custID);
  panel.add(customer);

  panel.add(reset);
  panel.add(purchase);

事件处理

actionPerformed 方法提供的是以下各种可能发生的应用事件 的特征:

  • PurchaseReset 按钮点击鼠标。
  • appleQntpeachQntpearQnt 域按下回车键。
除了仅对 actionPerformed 方法完整显示外,本节对其它方法仅介 绍 purchase 按钮和 pearQnt 文本域的行为。Reset 按钮与 purchase 按钮类似,而其它文本域的操作也类似于 pearQnt

Purchase 按钮: Purchase 按钮的作用是从 文本域和文本区中检索数据并把得到的数据发送给服务器程序。服务器程序可通过 其 Send 接口被 FruitClient 程序调用,该接口声明了用于发送和 接收数据的远程服务器方法。

.

send 变量是 Send 接口的一个实例。该实例在 FruitClient 程序的 main 方法中创建。send 变量在 FruitClient 程序中被声明为静态和全局变量,这样静态 main 方法就可以对它进行实例化并使其可被 actionPerformed 方法访问。

if(source == purchase){
  cardnum = creditCard.getText();
  custID = customer.getText();
  apples = appleqnt.getText();
  peaches = peachqnt.getText();
  pears = pearqnt.getText();
  try{
     send.sendCreditCard(cardnum);
     send.sendCustID(custID);
     send.sendAppleQnt(apples);
     send.sendPeachQnt(peaches);
     send.sendPearQnt(pears);
     send.sendTotalCost(icost);
     send.sendTotalItems(itotal);
  } catch (Exception e) {
     System.out.println("Cannot send data to server"); 
  }
}
pearQnt 文本域: 文本域 pearQnt 的作用是: 获取终端用户欲订购的梨的数量,把得到的数据添加到总数量项中,用该数量计算所需费 用,然后再将其添加到总费用项中。本代码中有两个很有意义的作用:控制光标位置和把 字符串信息转换成计算所需要的数字信息。这两个方面都将在下文进行讨论。

if(source == pearqnt){
  number = pearqnt.getText();
  if(number.length() > 0){
    pearsNo = Integer.valueOf(number);
    itotal += pearsNo.intValue();
    pearqnt.setNextFocusableComponent(creditCard);
  } else {
    itotal += 0;
    pearqnt.setNextFocusableComponent(creditCard);
  }
}

光标焦点

终端用户可以在用户界面中利用 Tab 键把光标从一个组件移动到另一个组件。 Tab 键的默认动作是在包括文本区在内的所有用户界面组件之间逐步移动。

由于终端用户并不与文本区互操作,所以光标就不需要进入文本区。示例程序 中包含一个在其构造函数中对pearqnt.setNextFocusableComponent 方法的调用,用于在按下 Tab 键时使光标从 pearqnt 文本域移动 到 creditcard 文本域而绕过总费用和总数量这两个文本区。

  applechk = new JLabel("   Apples");
  appleqnt = new JTextField();
  appleqnt.addActionListener(this);

  pearchk = new JLabel("   Pears");
  pearqnt = new JTextField();
  pearqnt.addActionListener(this);

  peachchk = new JLabel("   Peaches");
  peachqnt = new JTextField();
  peachqnt.addActionListener(this);

  cardNum = new JLabel("   Credit Card:");
  creditCard = new JTextField();
//Make cursor go to creditCard component
  pearqnt.setNextFocusableComponent(creditCard);

  customer = new JTextField();
  custID = new JLabel("   Customer ID:");

把字符串转换成数值或相反

若要计算订购数量及订购费用,就必须把从 appleQntpeachQntpearQnt 文本域中获得的字符串转换 成相应的数值。

字符串的值由 number 变量返回。若要保证用户确实输入了数 值,就必须检查串的长度。如果长度值不大于零,那么终端用户按下回车键前就 没有输入任何值。此种情况下,else 语句就会向总计栏中加一个 零,然后把光标放置到 creditCard 文本域中。加零操作实际上并 不是必需的,但确实能够使代码更易于为阅读者所理解。

如果串长度值大于零,就会由串创建 java.lang.Integer 类的 实例。随后,程序会调用 Integer.intValue() 方法来生成与该串 的值等效的整数(int),从而能够将它与 itotal 整型变量中的总数量值相加。

if(number.length() > 0){
  pearsNo = Integer.valueOf(number);
  itotal += pearsNo.intValue();
} else {
  itotal += 0;
}
若要显示各文本区的累计数量及费用总和,就必须把得到的总和值转换成字符串 的形式。方法 actionPerformed 末尾的程序代码就是用来完成这 一操作的。

若要显示总数量,就需要利用 itotal 整型变量生成一个 java.lang.Integer 对象。程序调用 Integer.toString 方法来生成 与整型数据等效的字符串。该字符串被传递给 this.cost.setText(text2) 调用,以更新显示器上的 Total Cost(总费用)域。


注意:cost文本区变量的引用名为 this.cost, 这是因为actionPerformed 方法也有一个 Double 型的 cost 变量。为了只引用文本区全局变量而不引用同一变量名的 Double 型局部变量,就必须以 this.cost 变量名进行引用。
  num = new Integer(itotal);
  text = num.toString();
  this.items.setText(text);

  icost = (itotal * 1.25);
  cost = new Double(icost);
  text2 = cost.toString();
  this.cost.setText(text2);
到目前为止,示例中使用的全部数据类型都已是类了。但是 intdouble 这两种数据类型并不是类,它们是基本数据类型。

.

数据类型 int 包含一个32 的位整数值,或为正数或为负数。 您可以用标准的算术运算符(+、-、* 和 /)对这个整数进行算术运算。

Integer 类不仅包含一个或正或负的32 位整数值,而且提供了 对该值的操作方法。例如,Integer.intValue 方法使您能够把一个 Integer 转换整数值来进行算术运算。

.

数据类型 double 包含一个 64 位的双精度浮点型值。而 Double 类不仅含有一个 64 位的双精度浮点型值,而且提供了对该 值的操作方法。例如,Double.doubleValue 方法使您能够把 Double 转换成双精度浮点型值进行算术运算。

服务器程序代码

服务器程序由 RemoteServer.java类组成。这些类实现在 Send.java接口中声明的方法。见第 1 部 分、第 8 课:远程方法调用,与在本课中的唯一不同是,需要声明和实现的 sendXXXgetXXX 方法更多,现列举如下:

  • public void sendCreditCard(String creditcard){cardnum = creditcard;}
  • public String getCreditCard(){return cardnum;}
  • public void sendCustID(String cust){custID = cust;}
  • public String getCustID(){return custID;}
  • public void sendAppleQnt(String apps){apples = apps;}
  • public String getAppleQnt(){return apples;}
  • public void sendPeachQnt(String pchs){ peaches = pchs;}
  • public String getPeachQnt(){return peaches;}
  • public void sendPearQnt(String prs){pears = prs;}
  • public String getPearQnt(){return pears;}
  • public void sendTotalCost(double cst){cost = cst;}
  • public double getTotalCost(){return cost; }
  • public int getTotalItems(){return items;}

值得特别注意的是,任意类型和大小的数据都可以使用 RMI API通过服务器从 一个客户机传递到另一个客户机。不需要针对大量数据的特殊处理,也不需要对不 同数据类型作特别考虑,而这些因素在使用网络接口通信时,有时会成为需要考虑 的问题。

查看订单的客户程序

OrderClient.java 类利用文本区和按钮来显示订单信息。

本程序代码与 FruitOrder.java 类非常类似,所以有许多内容与前 文所述重复,不过本节要强调用于查看订单的 actionPerformed 方法的两部分操 作。

第一部分是检索信用卡号和通过服务器订购的苹果、桃和梨的数量,并把这些 值置于对应的文本区中。

第二部分是检索数据类型分别为 doubleinteger 的总费用和总数量。然后把总费用转换成 java.lang.Double 对象 而把总数量转换成 java.lang.Integer 对象,并分别对它们调用 toString 方法来提取字符串的等效值。最后,用这些串值设定相 应文本区的值。


if(source == view){
 try{
//Retrieve and display text
  text = send.getCreditCard();
  creditNo.setText(text);

  text = send.getCustID();
  customerNo.setText(text);

  text = send.getAppleQnt();
  applesNo.setText(text);

  text = send.getPeachQnt();
  peachesNo.setText(text);

  text = send.getPearQnt();
  pearsNo.setText(text);

//Convert Numbers to Strings
  cost = send.getTotalCost();
  price = new Double(cost);
  unit = price.toString();
  icost.setText(unit);

  items = send.getTotalItems();
  itms = new Integer(items);
  i = itms.toString();
  itotal.setText(i);

 } catch (Exception e) {
  System.out.println("Cannot send data to server");
 }
}

程序的改进

本部分示例程序对水果订购客户而言有两个主要的设计缺陷。第一,为了进行 计算必须按回车键;第二,要处理订购苹果、桃和梨时输入非数字字符所引发的 错误。

计算和按回车键: 如果输入苹果、桃和梨的数量值后终端 用户没有按下回车键而进下一文本域,程序就不会执行计算操作。也就是说,终 端用户点击 Purchase(购买)键后,订单被发送,但是所发送的订货数量和费用 并不正确。因此,在此特定的应用中,需要依赖于回车键动作事件的设计并不是 好设计。

应该对 actionPerformed 方法进行修改,以使上述问题不再发 生。参考答案见 答案。看答案之前,请先亲自完成对方法的修改。

非数字字符错误: 如果终端用户在苹果、桃或梨域中输入 了非数字值,程序发生栈跟踪,指示出现非法数字格式。一个好的程序应该能够 捕获并处理这类错误,以避免发生栈跟踪。

提示: 要求您的程序能够指出代码中哪一部分代码抛出错 误,并把所发生的错误封装在 trycatch 程序 块中。trycatch 程序块的介绍见 第 1 部分、第 6 课:文件访问及权限。 需要捕捉的错误是 java.lang.NumberFormatException

在查看程序修改的参考 答案 前,请先亲自尝试。

更多信息

Java 教程 编写事件监视程序 课中,您将看到有关事件监视的更多信息。

Java 教程 变量及数据类型 部分提供了基本数据类型的更详细说明。

有关 Project Swing 的详细说明见 JFC Swing 教 程:GUI 构造指南

术语“Java virtual machine(Java 虚拟机)”和“JVM”在本网站中引 用时,是指 Java 平台虚拟机。

[TOP]

 

常见问答
下载中心
产品简介
 
 
Solaris论坛
 
   
 
null