Skip to content

5. 图形界面

本文将演示如何使用 JAVA 构建图形用户界面。 首先,我们将了解哪些基础类能够帮助我们构建图形用户界面。起初,我们不使用任何自动生成工具。随后,我们将使用 JBuilder——这是 Borland/Inprise 提供的一款开发工具,它能简化 Java 应用程序的开发,特别是图形用户界面的构建。

5.1. 图形界面的基础

5.1.1. 一个简单的窗口

请看以下代码:


// 导入的类
import javax.swing.*;
import java.awt.*;
    
    // 表单类
  public class form1 extends JFrame {
    
      // 构造函数
      public form1() {
            // 窗口标题
            this.setTitle("Mon premier formulaire");
            // 窗口尺寸
            this.setSize(new Dimension(300,100));
      }//构造函数
  
      // 测试函数
      public static void main(String[] args) {
            // 显示表单
            new form1().setVisible(true);
      }
  }//类

执行上述代码将显示以下窗口:

Image

图形界面通常继承自基类 JFrame


  public class form1 extends JFrame {

基类 JFrame 定义了一个基础窗口,包含关闭按钮、最大化/最小化按钮、可调整大小等功能,并管理这些图形对象的事件。 在此,我们通过为其设定标题、宽度(300 像素)和高度(100 像素)来对基类进行特化。这在构造函数中完成:


      // 构造函数
      public form1() {
            // 窗口标题
            this.setTitle("Mon premier formulaire");
            // 窗口尺寸
            this.setSize(new Dimension(300,100));
      }//构造函数

窗口标题由方法 setTitle 确定,其尺寸由方法 setSize 确定。 该方法接受一个 Dimension(largeur,hauteur) 对象作为参数,其中 largeurhauteur 分别表示以像素为单位的窗口宽度和高度。

方法 main 按以下方式启动图形应用程序:


            new form1().setVisible(true);

随后创建一个类型为 form1 的表单(new form1())并显示(setVisible(true)),然后应用程序开始监听表单上发生的事件(点击、鼠标移动等 ……),并执行表单所处理的事件。在此,我们的表单仅处理基类 JFrame 所支持的事件(关闭按钮点击、最大化/最小化、窗口大小调整、窗口移动等)。

当通过 DOS 窗口运行以下命令测试该程序时:

java form1

来执行文件 form1.class 时,会发现关闭显示的窗口后,DOS 窗口并未“恢复控制权”,仿佛程序尚未结束。事实确实如此。程序的执行过程如下:

  • 起初,会启动一个初始执行线程来执行方法 main
  • 当该方法创建表单并显示时,会创建第二个线程专门处理与表单相关的事件
  • 在表单创建完成后(在本例中),main方法的线程终止,此时仅剩图形用户界面的执行线程。
  • 关闭窗口时,窗口虽然消失,但并未中断其运行的线程
  • 目前,我们只能通过在启动程序的 DOS 窗口中按下 Ctrl-C 来终止该线程。

让我们验证是否存在两个独立的线程:一个用于执行 main 方法,另一个用于运行图形界面:


// 导入的类
import javax.swing.*;
import java.awt.*;
import java.io.*;

// 表单类
 public class form1 extends JFrame {
    
// 构造函数
 public form1() {
   // 窗口标题
   this.setTitle("Mon premier formulaire");
   // 窗口尺寸
   this.setSize(new Dimension(300,100));
}//构造函数
  
// 测试函数
 public static void main(String[] args) {
   // 跟踪
   System.out.println("Début du thread main");
   // 显示表单
   new form1().setVisible(true);
   // 跟踪
   System.out.println("Fin du thread main");  
}//主程序
}//类

执行后得到以下结果:

Image

可以看出,线程 main 已结束,而窗口仍显示在屏幕上。关闭窗口并不会终止该窗口所运行的线程。要停止该线程,需在 DOS 窗口中再次按下 Ctrl-C。

作为本示例的结尾,请注意以下导入的包:

  • javax.swing 对应类 JFrame
  • java.awt 对应类 Dimension

5.1.2. 处理事件

在上一个示例中,我们需要自行处理窗口关闭事件,以便在发生时停止应用程序,而目前尚未实现这一点。为此,我们需要创建一个对象来“监听”窗口上发生的事件,并检测“窗口关闭”事件。 该对象被称为“监听器”或事件处理器。针对图形用户界面组件上可能发生的各种事件,存在不同类型的处理程序。对于组件 JFrame,其监听器名为 WindowListener,这是一个定义了以下方法的接口(参见 Java 文档)

方法摘要
 
void
windowActivated(WindowEvent e)
          该窗口成为活动窗口
 
void
windowClosed(WindowEvent e)
          窗口已关闭
 
void
windowClosing(WindowEvent e)
          用户或程序请求关闭窗口
 
void
windowDeactivated(WindowEvent e)
          该窗口不再是活动窗口
 
void
windowDeiconified(WindowEvent e)
          窗口从最小化状态恢复为正常状态
 
void
windowIconified(WindowEvent e)
          窗口从正常状态切换为最小化状态
 
void
windowOpened(WindowEvent e)
          窗口首次显示
 

因此共有七个可处理的事件。这些处理程序均接收一个类型为 WindowEvent 的对象作为参数,目前我们暂不关注该对象。 我们在此关注的事件是窗口关闭,该事件应由方法 windowClosing 处理。要处理此事件,我们可以使用匿名类以如下方式创建一个 Windowlistener 对象:


   // 创建事件处理程序
   WindowListener win=new WindowListener(){
     public void windowActivated(WindowEvent e){}
     public void windowClosed(WindowEvent e){}
     public void windowClosing(WindowEvent e){System.exit(0);}
     public void windowDeactivated(WindowEvent e){}
     public void windowDeiconified(WindowEvent e){}
     public void windowIconified(WindowEvent e){}
     public void windowOpened(WindowEvent e){}
   };//Win 定义

我们的事件处理程序实现了 WindowListener 接口,必须定义该接口的七个方法。由于我们只想处理窗口关闭事件,因此仅为 windowClosing 方法编写代码。 当其他事件发生时,我们会收到通知,但不会采取任何行动。当收到窗口正在关闭的通知(windowClosing)时,我们将采取什么措施?我们将终止应用程序:


     public void windowClosing(WindowEvent e){System.exit(0);}

这里有一个能够处理一般窗口事件的对象。如何将其与特定窗口关联起来? 类 JFrame 提供了一个方法 addWindowListener(WindowListener win),用于将“窗口”事件处理程序与特定窗口关联。因此,在此处,我们在窗口的构造函数中将编写:


   // 创建事件管理器
   WindowListener win=new WindowListener(){
     public void windowActivated(WindowEvent e){}
     public void windowClosed(WindowEvent e){}
     public void windowClosing(WindowEvent e){System.exit(0);}
     public void windowDeactivated(WindowEvent e){}
     public void windowDeiconified(WindowEvent e){}
     public void windowIconified(WindowEvent e){}
     public void windowOpened(WindowEvent e){}
   };//Windows 定义
    // 该事件处理程序将处理当前窗口的事件
   this.addWindowListener(win);

完整程序如下:


// 导入的类
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;

// 表单类
 public class form2 extends JFrame {
    
// 构造函数
 public form2() {
   // 窗口标题
   this.setTitle("Mon premier formulaire");
   // 窗口尺寸
   this.setSize(new Dimension(300,100));
   // 创建事件处理程序
   WindowListener win=new WindowListener(){
     public void windowActivated(WindowEvent e){}
     public void windowClosed(WindowEvent e){}
     public void windowClosing(WindowEvent e){System.exit(0);}
     public void windowDeactivated(WindowEvent e){}
     public void windowDeiconified(WindowEvent e){}
     public void windowIconified(WindowEvent e){}
     public void windowOpened(WindowEvent e){}
   };//Win 定义
   // 该事件处理程序将处理当前窗口的事件
   this.addWindowListener(win);
}//构造函数
  
// 测试函数
 public static void main(String[] args) {
   // 显示表单
   new form2().setVisible(true);
}//主函数
}//类

java.awt.event 包含接口 WindowListener。当运行该程序并关闭弹出的窗口时,会在启动程序的 DOS 窗口中看到程序已结束执行,而此前并未出现这种情况。

在我们的程序中,创建负责管理窗口事件的对象有些繁琐,因为我们不得不为那些不想处理的事件也定义方法。 在这种情况下,与其使用 WindowListener 接口,不如使用 WindowAdapter 。该类实现了 WindowListener 接口,并包含七个空方法。 通过继承类 WindowAdapter 并仅重写我们感兴趣的方法,我们可以获得与接口 WindowListener 相同的结果,但无需定义那些我们不感兴趣的方法。

  • 定义事件处理程序
  • 将处理程序关联到窗口

的步骤,在我们的示例中可以如下实现:


   // 创建事件处理程序
   WindowAdapter win=new WindowAdapter(){
     public void windowClosing(WindowEvent e){System.exit(0);}
   };//win 定义
   // 该事件处理程序将处理当前窗口的事件
   this.addWindowListener(win);

这里我们使用了一个匿名类,该类继承自类 WindowAdapter 并重写了其方法 windowClosing。程序代码如下:


// 导入的类
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;

// 表单类
 public class form2 extends JFrame {
    
// 构造函数
 public form2() {
   // 窗口标题
   this.setTitle("Mon premier formulaire");
   // 窗口尺寸
   this.setSize(new Dimension(300,100));
   // 创建事件处理程序
   WindowAdapter win=new WindowAdapter(){
     public void windowClosing(WindowEvent e){System.exit(0);}
   };//Win 定义
   // 该事件处理程序将处理当前窗口的事件
   this.addWindowListener(win); }//构造函数
  
// 测试函数
 public static void main(String[] args) {
   // 显示表单
   new form2().setVisible(true);
}//主函数
}//类

它与前一个程序给出的结果相同,但编写起来更简单。

5.1.3. 带按钮的表单

现在,让我们在窗口中添加一个按钮:


// 导入的类
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;

// 表单类
 public class form3 extends JFrame {
 
     // 一个按钮
  JButton btnTest=null;
  Container conteneur=null;
  
  // 构造函数
  public form3() {
     // 窗口标题
    this.setTitle("Formulaire avec bouton");
     // 窗口尺寸
    this.setSize(new Dimension(300,100));
    // 创建事件处理程序
    WindowAdapter win=new WindowAdapter(){
     public void windowClosing(WindowEvent e){System.exit(0);}
    };//win 定义
     // 该事件处理程序将处理当前窗口的事件
    this.addWindowListener(win);
     // 获取窗口的容器
    conteneur=this.getContentPane();
     // 选择该容器中组件的布局管理器
    conteneur.setLayout(new FlowLayout());
    // 创建一个按钮
    btnTest=new JButton();
    // 设置其标签
    btnTest.setText("Test");
    // 将按钮添加到容器中
    conteneur.add(btnTest);
  }//构造函数
  
  // 测试函数
 public static void main(String[] args) {
    // 显示表单
   new form3().setVisible(true);
}//主函数
}//类

JFrame 窗口包含一个容器,用户可以在其中放置图形组件(按钮、复选框、下拉列表等)。可以通过 JFrame 类的 getContentPane 方法访问该容器:


  Container conteneur=null;
..........
     // 获取窗口容器
    conteneur=this.getContentPane();

所有组件均通过类 Container 的方法 add 放入容器中。因此,若要将组件 C 放入前面的对象 conteneur 中,应编写如下代码:

conteneur.add(C)

该组件在容器中的具体位置在哪里?存在多种名为 XXXLayout 的组件布局管理器,例如 XXX 与 BorderFlow 等 ……每个布局管理器都有其独特之处。例如FlowLayout管理器会从表单顶部开始,将组件按行排列。当一行填满后,组件将移至下一行。 要将布局管理器与窗口 JFrame 关联,需使用类 JFrame 中的方法 setLayout,其调用形式为:

    setLayout(objet XXXLayout);

因此,在本例中,为了将类型为 FlowLayout 的处理程序关联到该窗口,我们编写了:


     // 选择该容器中组件的布局管理器
    conteneur.setLayout(new FlowLayout());

也可以不使用布局处理器,直接写为:

    setLayout(null);

在这种情况下,需以 (x,y,宽度,高度) 的形式提供组件在容器中的精确坐标,其中 (x,y) 表示组件在容器左上角的坐标。后续我们通常会采用这种方法。

现在我们已经知道组件是如何放置在容器(add)中的,以及它们的位置(setLayout)。接下来我们需要了解

哪些组件可以放置在容器中。在此,我们将放置一个由类 javax.swing.JButton 实现的按钮:



  JButton btnTest=null;
..........
     // 创建一个按钮
    btnTest=new JButton();
    // 设置其标签
    btnTest.setText("Test");
    // 将按钮添加到容器中
    conteneur.add(btnTest);

测试该程序时,会显示以下窗口:

Image

如果调整上述表单的大小,容器的布局管理器会自动被调用以重新定位组件:

Image

这正是布局管理器的核心价值:在容器尺寸发生变化时,始终保持组件布局的一致性。让我们使用布局管理器 null 来观察其中的差异。现在,按钮通过以下指令被放置在容器中:


// 选择该容器中的组件样式管理器
conteneur.setLayout(null);
    // 创建一个按钮
btnTest=new JButton();
    // 设置其标签
btnTest.setText("Test");
    // 设置其位置和尺寸
btnTest.setBounds(10,20,100,20);
     // 将按钮添加到容器中
conteneur.add(btnTest);

在此,我们将按钮明确放置在表单的 (10,20) 位置,并将其尺寸设置为宽 100 像素、高 20 像素。新窗口如下所示:

Image

如果调整窗口大小,按钮仍保持在原位。

Image

如果点击按钮 Test,则不会发生任何变化。这是因为我们尚未为该按钮关联事件处理程序。 要了解某个组件可用的事件处理程序类型,可以在其类定义中查找 addXXXListener 方法,这些方法允许将事件处理程序关联到该组件。 类 javax.swing.JButton 继承自类 javax.swing.AbstractButton,其中包含以下方法:

方法摘要
 
void
addActionListener(ActionListener l)
 
void
addChangeListener(ChangeListener l)
 
void
addItemListener(ItemListener l)
 

这里需要查阅文档以确定哪个事件处理程序负责处理按钮点击事件。该接口为 ActionListener。该接口仅定义了一个方法:

方法摘要
 
void
actionPerformed(ActionEvent e)
 

该方法接收一个 ActionEvent 参数,我们暂时忽略它。为了处理程序中 btntest 按钮的点击事件,首先为其关联一个事件处理程序:


        btnTest.addActionListener(new ActionListener()
         {
                public void actionPerformed(ActionEvent evt){
                  btnTest_clic(evt);
              }
            }//匿名类
    );//事件处理程序

在此,方法 actionPerformed 引用了方法 btnTest_clic,我们将其定义如下:


public void btnTest_clic(ActionEvent evt){
  // 控制台跟踪
System.out.println("clic sur bouton");
}//btnTest_click

每当用户点击 Test 按钮时,系统都会在控制台输出一条消息。如下面的执行结果所示:

Image

完整的程序如下:


// 导入类
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;

// 表单类
 public class form4 extends JFrame {
 
     // 一个按钮
  JButton btnTest=null;
  Container conteneur=null;
  
  // 构造函数
  public form4() {
     // 窗口标题
    this.setTitle("Formulaire avec bouton");
     // 窗口尺寸
    this.setSize(new Dimension(300,100));
    // 创建事件处理程序
    WindowAdapter win=new WindowAdapter(){
     public void windowClosing(WindowEvent e){System.exit(0);}
    };//win 定义
     // 该事件处理程序将处理当前窗口的事件
    this.addWindowListener(win);
     // 获取窗口的容器
    conteneur=this.getContentPane();
     // 选择该容器中组件的布局管理器
    conteneur.setLayout(null);
    // 创建一个按钮
    btnTest=new JButton();
    // 设置其标签
    btnTest.setText("Test");
    // 设置其位置和尺寸
    btnTest.setBounds(10,20,100,20);
     // 为其关联一个事件处理程序
    btnTest.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent evt){
              btnTest_clic(evt);
          }
        }//匿名类
    );//事件处理程序
     // 将按钮添加到容器中
    conteneur.add(btnTest);
  }//构造函数

    public void btnTest_clic(ActionEvent evt){
       // 控制台跟踪
    System.out.println("clic sur bouton");
  }//btnTest_click
    
  // 测试功能
 public static void main(String[] args) {
    // 显示表单
   new form4().setVisible(true);
}//主程序
}//类

5.1.4. 事件处理程序

我们将介绍的主要 Swing 组件包括窗口(JFrame)、按钮(JButton)、复选框(JCheckBox)、 单选按钮(JButtonRadio)、下拉列表(JComboBox)、列表(JList)、滑块(JScrollBar)、 标签(JLabel)、单行输入框(JTextField)或多行输入框(JTextArea)、菜单(JMenuBar), 菜单项(JMenuItem)。

下表列出了部分事件处理程序及其关联的事件。

处理程序
组件
注册方法
事件
ActionListener
JButton、JCheckbox、JButtonRadio、JMenuItem
public void addActionListener(ActionListener)
单击按钮、复选框、单选按钮、菜单项
 
JTextField
ItemListener
JComboBox、JList
public void addItemListener(ItemListener)
所选元素已更改
InputMethodListener
JTextField, JTextArea
public void addMethodInputListener(InputMethodListener)
输入框中的文本已更改,或输入光标位置已发生变化
CaretListener
JTextField, JTextArea
public void addcaretListener(CaretListener)
输入光标位置已改变
AdjustmentListener
JScrollBar
public void addAdjustmentListener(AdjustmentListener)
变频器数值已更改
MouseMotionListener
 
public void addMouseMotionListener(MouseMotionListener)
鼠标移动
WindowListener
JFrame
public void addWindowlistener(WindowListener)
窗口事件
MouseListener
 
public void addMouselistener(MouseListener)
鼠标事件(点击、进入/离开组件区域、按下按钮、释放)
FocusListener
 
public void addFocuslistener(FocusListener)
焦点事件(获得、失去)
KeyListener
 
public void addKeylistener(KeyListener)
键盘事件(按键输入、按下、松开)
组件
事件处理程序注册方法
JButton
public void addActionListener(ActionListener)
JCheckbox
public void addItemListener(ItemListener)
JCheckboxMenuItem
public void addItemListener(ItemListener)
JComboBox
public void addItemListener(ItemListener)
public void addActionListener(ActionListener)
Container
public void addContainerListener(ContainerListener)
JComponent
public void addComponentListener(ComponentListener)
public void addFocusListener(FocusListener)
public void addKeyListener(KeyListener)
public void addMouseListener(MouseListener)
public void addMouseMotionListener(MouseMotionListener)
JFrame
public void addWindowlistener(WindowListener)
JList
public void addItemListener(ItemListener)
JMenuItem
public void addActionListener(ActionListener)
JPanel
作为容器
JScrollPane
作为容器
JScrollBar
public void addAdjustmentListener(AdjustmentListener)
JTextComponent
public void addInputMethodListener(InputMethodListener)
public void addCaretListener(CaretListener)
JTextArea
如 JTextComponent
JTextField
如 JTextComponent
public void addActionListener(ActionListener)

除类型为 TextXXX 的组件外,所有组件均继承自类 JComponent,因此也拥有该类相关的所有方法。

5.1.5. 事件处理程序的方法

下表列出了各个事件处理程序必须实现的方法。

接口
方法
ActionListener
public void actionPerformed(ActionEvent)
AdjustmentListener
public void adjustmentValueChanged(AdjustmentEvent)
ComponentListener
public void componentHidden(ComponentEvent)
public void componentMoved(ComponentEvent)
public void componentResized(ComponentEvent)
public void componentShown(ComponentEvent)
ContainerListener
public void componentAdded(ContainerEvent)
public void componentRemoved(ContainerEvent)
FocusListener
public void focusGained(FocusEvent)
public void focusLost(FocusEvent)
ItemListener
public void itemStateChanged(ItemEvent)
KeyListener
public void keyPressed(KeyEvent)
public void keyReleased(KeyEvent)
public void keyTyped(KeyEvent)
MouseListener
public void mouseClicked(MouseEvent)
public void mouseEntered(MouseEvent)
public void mouseExited(MouseEvent)
public void mousePressed(MouseEvent)
public void mouseReleased(MouseEvent)
MouseMotionListener
public void mouseDragged(MouseEvent)
public void mouseMoved(MouseEvent)
TextListener
public void textValueChanged(TextEvent)
InputmethodListener
public void InputMethodTextChanged(InputMethodEvent)
public void caretPositionChanged(InputMethodEvent)
CaretLisetner
public void caretUpdate(CaretEvent)
WindowListener
public void windowActivated(WindowEvent)
public void windowClosed(WindowEvent)
public void windowClosing(WindowEvent)
public void windowDeactivated(WindowEvent)
public void windowDeiconified(WindowEvent)
public void windowIconified(WindowEvent)
public void windowOpened(WindowEvent)

5.1.6. 适配器类

正如我们在 WindowListener 接口中看到的那样,存在名为 XXXAdapter 的类,它们通过空方法实现了 XXXListener 接口。 因此,从类 XXXAdapter 派生的事件处理程序只需实现接口 XXXListener 中的部分方法,即应用程序所需的方法。

假设我们要处理 Frame f1 组件上的鼠标点击事件。可以通过以下方式为其关联一个事件处理程序:

    f1.addMouseListener(new gestionnaireSouris());

并编写:


    public class gestionnaireSouris implements MouseListener{
         // 编写接口的 5 个方法MouseListener
        // mouseClicked, ..., mouseReleased)
    }// 类结束

由于我们只想处理鼠标点击,因此最好写成:


public class gestionnaireSouris extends MouseAdapter{
        // 编写一个方法,用于处理鼠标点击
        public void mouseClicked(MouseEvent evt){

        }
}// 类结束

下表列出了不同事件处理程序的适配器类:

事件处理程序
适配器
ComponentListener
ComponentAdapter
ContainerListener
ContainerAdapter
FocusListener
FocusAdapter
KeyListener
KeyAdapter
MouseListener
MouseAdapter
MouseMotionListener
MouseMotionAdapter
WindowListener
WindowAdapter

5.1.7. 结论

我们刚刚介绍了在 Java 中创建图形界面的基本概念:

  • 创建窗口
  • 创建组件
  • 使用布局管理器将组件与窗口关联
  • 将事件处理程序与组件关联

现在,与其像刚才那样“手动”构建图形用户界面,我们将使用 Borland/Inprise 公司的 Java 开发工具 JBuilder(第 4 版),并使用 Java 创建者 Sun 目前推荐的 java.swing 库中的组件。

5.2. 使用 JBuilder 构建图形用户界面

5.2.1. 我们的第一个JBuilder项目

为了熟悉 JBuilder,让我们构建一个非常简单的应用程序:一个空白窗口。

  1. 启动JBuilder,选择“文件/新建项目”选项。此时将显示向导的第一页:

Image

  1. 请填写以下字段:
项目名称
开始
这将生成一个名为 début.jpr 的项目文件,并保存在“项目目录名称”字段中指定的文件夹下
根路径
请指定要创建的项目文件夹所在的父文件夹。
项目目录名称
请指定存放所有项目文件的文件夹名称。该文件夹将创建在“根路径”字段中指定的目录下

源文件(.java、.html 等)、目标文件(.class 等)以及备份文件可存放在不同的目录中。若将相关字段留空,这些文件将与项目文件存放在同一目录下。

请输入 [suivant]

  1. 系统将显示确认界面,确认前一步的选项

Image

执行 [suivant]

  1. 新界面将提示您为项目命名:

Image

执行 [terminer]

  1. 请确认已勾选选项 Voir/projet。您应该能在左侧窗口中看到项目的结构。

Image

  1. 现在,让我们在这个项目中构建一个图形用户界面。选择选项 Fichier/Nouveau/Application

Image

classe 字段中,输入将要创建的类名。这里我们采用了与项目相同的名称。

执行 [suivant]

  1. 随后将显示以下界面:

Image

interfaceDébut
这是将要构建的窗口对应的类名称
标题
这是将显示在窗口标题栏中的文本

请注意,在上文中,我们已设置该窗口在应用程序启动时居中显示。

输入 [Terminer]

  1. 现在正是 JBuilder 大显身手的时候。
  • 它会生成两个指定名称类的 .java 源文件:应用程序类和图形用户界面类。在左侧窗口的项目结构中,可以看到这两个文件

Image

  • 要查看这两个类生成的代码,只需双击相应的 .java 文件即可。我们稍后会再讨论生成的代码。
  • 请确认已勾选选项 Voir/Structure。该选项可显示当前所选类(双击 .java 文件)的结构。例如,以下是类 début 的结构:

Image

在此可了解:

  • 导入的库(imports

  • 存在构造函数 début()

  • 存在一个静态方法 main()

  • 存在一个属性 packFrame

了解类结构有什么用?

  • 您可以全面了解该结构。如果您的类比较复杂,这会很有用。

  • 您可以在类结构窗口中点击某个方法,直接访问其代码。如果您的类有数百行代码,这同样非常有用。您无需逐行浏览才能找到所需的代码。

JBuilder生成的代码已经可以使用。执行“运行/运行项目”或 F9,您将看到所需的窗口:

Image

此外,点击窗口关闭按钮时,它会正常关闭。我们刚刚构建了第一个图形用户界面。可以通过“文件/关闭项目”选项保存项目:

Image

按下 Tous,上述窗口中的所有项目都会被勾选。OK 将关闭它们。 如果我们好奇地使用Windows资源管理器进入项目文件夹(即在项目配置开始时向向导指定的那个文件夹),会发现其中包含以下文件:

Image

在本示例中,我们要求所有文件(源文件 .java、目标文件 .class、备份文件 .jpr~)都位于与项目文件 .jpr 相同的文件夹内。

5.2.2. JBuilder为图形用户界面生成的文件

现在让我们好奇地看看 JBuilder 生成了哪些 .java 源文件。了解如何阅读生成的代码非常重要,因为大多数情况下我们需要在生成的代码基础上添加新代码。首先打开我们的项目:文件/打开项目,然后选择项目 début.jpr。 我们看到了之前构建的项目。

5.2.2.1. 主类

在显示项目文件列表的窗口中双击类名 début.java,查看该类。我们看到如下代码:

import javax.swing.UIManager;
import java.awt.*;

public class début {
  boolean packFrame = false;

   /**构建应用程序*/
  public début() {
    interfaceDébut frame = new interfaceDébut();

     //验证具有预定义尺寸的框架
     //压缩具有首选尺寸信息的框架——例如根据其布局
    if (packFrame) {
      frame.pack();
    }
    else {
      frame.validate();
    }
    //将窗口居中
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension frameSize = frame.getSize();
    if (frameSize.height > screenSize.height) {
      frameSize.height = screenSize.height;
    }
    if (frameSize.width > screenSize.width) {
      frameSize.width = screenSize.width;
    }
    frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
    frame.setVisible(true);
  }
   /**主方法*/
  public static void main(String[] args) {
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch(Exception e) {
      e.printStackTrace();
    }
    new début();
  }
}

让我们来分析一下生成的代码:

  1. 函数 main 设置窗口的外观(setLookAndFeel),并创建类 début 的一个实例。
  2. 随后执行构造函数 début()。它创建了窗口类的一个实例 framenew interfaceDébut())。该实例已被构建但尚未显示。
  3. 随后,根据窗口各组件(frame.validate)的可用信息对窗口进行尺寸调整。此时,该窗口开始独立运行,显示在屏幕上并响应用户的操作。
  4. 该窗口位于屏幕中央,这是因为我们在使用向导配置窗口时已指定了此设置。

让我们看看如果将代码 début.java 精简到最低限度(就像本章开头那样)会得到什么结果。创建一个新类。选择“文件/新建类”选项:

Image

将新类命名为 début2,并将 [Terminer] 保留。项目中会出现一个新文件:

Image

文件 début2.java 已简化为最简形式:

public class début2 {
}

按以下方式完善该类:

public class début2 {
   // 主函数
  public static void main(String args[]){
    // 创建窗口
    new interfaceDébut().show();    // 或 new interfaceDébut.setVisible(true);
  }//main
}//类 début2

函数 main 会创建窗口 interfaceDébut 的实例并将其显示出来(show)。 在运行我们的项目之前,需要说明的是,包含待执行函数 main 的类现在是 début2。 右键单击项目 début.jpr,选择选项 propriétés,然后选择选项卡 Exécution

21

Image

此处显示主类为 début (1)。点击按钮 (2) 选择另一个主类:

Image

选择 début2,并将 [OK] 设为主要类。

Image

输入 [OK] 确认此选择,然后通过“执行/执行项目”或 F9 请求执行该项目。 此时出现的窗口与类 début 生成的窗口相同,只是该窗口未居中,因为此处未进行居中设置。此后,我们将不再展示 JBuilder 生成的主类,因为它始终执行相同的功能:创建一个窗口。接下来,我们将重点关注该窗口的类。

5.2.2.2. 窗口类

现在让我们看看为类 interfaceDébut 生成的代码:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class interfaceDébut extends JFrame {
  JPanel contentPane;
  BorderLayout borderLayout1 = new BorderLayout();

  /**构建框架*/
  public interfaceDébut() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(borderLayout1);
    this.setSize(new Dimension(400, 300));
    this.setTitle("Ma première interface graphique avec Jbuilder");
  }
   /**已替换,因此窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
    super.processWindowEvent(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.exit(0);
    }
  }
}

5.2.2.2.1. 导入的库
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

这些库包括 java.awtjava.awt.eventjavax.swing。 前两个是早期 Java 版本中唯一可用于构建图形界面的库。javax.swing 库是较新的版本。在此,它对于此处使用的 JFrame 类型窗口是必需的。

5.2.2.2.2. 属性
  JPanel contentPane;
  BorderLayout borderLayout1 = new BorderLayout();

JPanel 是一种容器类型,可用于放置组件。BorderLayout 是可用于将组件放置在容器中的布局管理器类型之一。 在所有示例中,我们将不使用布局管理器,而是自行将组件放置在容器的特定位置。为此,我们将使用布局管理器 null

5.2.2.2.3. 窗口构建器
   /**构建框架*/
  public interfaceDébut() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(borderLayout1);
    this.setSize(new Dimension(400, 300));
    this.setTitle("Ma première interface graphique avec Jbuilder");
  }
  1. 构造函数首先声明将处理窗口(enableEvents)上的事件,然后调用方法 jbInit
  2. 获取窗口(JFrame)的容器(JPanel)(getContentPane
  3. 设置了格式化管理器(setLayout
  4. 窗口大小已确定(setSize
  5. 窗口标题已确定 (setTitle)

5.2.2.2.4. 事件管理器
   /**已替换,以便在窗口关闭时退出*/
  protected void processWindowEvent(WindowEvent e) {
    super.processWindowEvent(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.exit(0);
    }

构造函数曾指出该类将处理窗口事件。负责此工作的正是方法 processWindowEvent。 它首先将接收到的 WindowEvent 事件传递给其父类(JFrame),然后如果该事件是点击关闭窗口按钮触发的 WINDOW_CLOSING 事件,则应用程序将终止。

5.2.2.2.5. 结论

该窗口类的代码与本章开头示例中的代码不同。如果我们使用 JBuilder 以外的其他 Java 代码生成工具,生成的代码无疑会有所不同。在实际操作中,我们将直接采用 JBuilder 生成的代码来构建窗口,以便专注于编写图形用户界面的事件处理程序。

5.2.3. 绘制图形用户界面

5.2.3.1. 一个示例

在之前的示例中,我们尚未在窗口中放置任何组件。现在,让我们构建一个包含按钮、标签和输入框的窗口:

Image

字段如下:

编号
名称
类型
角色
1
lblSaisie
JLabel
名称
2
txtSaisie
JTextField
一个输入框
3
cmdAfficher
JButton
在对话框中显示输入框的内容txtSaisie

按照前一个项目的做法,构建项目 interface2.jpr,但暂时不要在窗口中放置任何组件。

Image

在上图窗口中,选择窗口的类 interface2.java。该窗口右侧有一个带标签页的文件夹:

Image

Source 选项卡可访问类 interface2.java 的源代码。 Conception 标签页用于可视化构建窗口。请选择该标签页。您眼前所见的是窗口容器,您将在此放置组件。目前该容器为空。左侧窗口显示了类的结构:

Image

this
代表窗口
contentPane
用于存放组件的容器,以及这些组件在容器中的布局方式(默认值为BorderLayout)
borderLayout1
一个格式管理器的实例

选择对象 this。其属性窗口随即在右侧显示:

Image

其中一些属性值得注意:

background
用于设置窗口的背景色
foreground
用于设置窗口内图形的颜色
JMenuBar
用于为窗口关联菜单
title
为窗口设置标题
resizable
用于指定窗口类型
font
用于设置窗口内文本的字体

this 对象仍处于选中状态时,可以通过拖动容器周围的锚点来调整屏幕上显示的容器大小:

Image

现在我们可以将组件拖放到上述容器中了。在此之前,我们需要先更改格式管理器。请在结构窗口中选中对象 contentPane

Image

然后在该对象的属性窗口中,选择属性 layout,并从可用值中选择 null

Image

由于没有格式管理器,我们可以自由地在容器中放置组件。现在是时候选择这些组件了。

选中 Conception 窗格后,设计窗口顶部的标签式文件夹中将显示可用组件:

Image

为了构建图形界面,我们拥有 swing (1) 和 awt (2) 组件。这里我们将使用 swing 组件。 在上面的组件栏中,选择一个 JLabel 组件 (3)、一个 JTextField 组件 (4) 和一个 JButton 组件 (5),并将它们放置在设计窗口的容器中。

Image

现在,让我们自定义这 3 个组件:

  • 标签(JLabel)jLabel1

选中该组件以打开其属性窗口。在该窗口中,修改以下属性:namelblSaisie,text:输入

  • 输入字段(JTextField)jTextfield1

选择该组件以打开其属性窗口。在该窗口中,修改以下属性:nametxtSaisie,text:留空

  • 按钮(JButton):namecmdAfficher,text:显示

现在我们看到如下窗口:

Image

以及以下结构:

Image

我们可以运行 (F9) 项目,以初步预览窗口的运行效果:

Image

关闭该窗口。接下来我们需要编写与点击按钮 Afficher 相关的过程。选中该按钮以打开其属性窗口。该窗口包含两个选项卡:propriétésévénements。 选择 événements

Image

窗口左侧列出了该按钮可能发生的事件。单击按钮对应事件 actionPerformed。右侧列显示了发生相应事件时调用的过程名称。单击事件 actionPerformed 右侧的单元格:

Image

JBuilder 会为每个事件处理程序生成一个默认名称,格式为 nomComposant_nomEvénement,例如 cmdAfficher_actionPerformed。 我们可以删除默认名称并输入其他名称。要查看 cmdAfficher_actionPerformed 处理程序的代码,只需双击上方的名称即可。系统将自动跳转到类的源代码窗格,并定位到事件处理程序代码的骨架部分:

  void cmdAfficher_actionPerformed(ActionEvent e) {
 }

接下来只需完善该代码即可。在此,我们希望显示一个对话框,其中包含字段 txtSaisie 的内容:

  void cmdAfficher_actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(this, "texte saisi="+txtSaisie.getText(), 
"Vérification de la saisie",JOptionPane.INFORMATION_MESSAGE);
 }

JOptionPanejavax.swing 库中的一个类。它允许显示带有图标的提示信息,或向用户请求信息。在此,我们使用该类的静态方法:

Image

parentComponent
对话框的“父”容器对象:此处为 this
message
要显示的对象。此处为输入字段的内容
title
对话框的标题
messageType
要显示的消息类型。决定对话框中消息旁边的图标。可能的值:
INFORMATION_MESSAGE、QUESTION_MESSAGE、ERROR_MESSAGE、WARNING_MESSAGE、PLAIN_MESSAGE

运行我们的应用程序(F9):

Image

Image

5.2.3.2. 窗口类的代码

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import javax.swing.event.*;


public class interface2 extends JFrame {
  JPanel contentPane;
  JLabel lblSaisie = new JLabel();
  JTextField txtSaisie = new JTextField();
  JButton cmdAfficher = new JButton();

  /**构建框架*/
  public interface2() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
    lblSaisie.setText("Saisie");
    lblSaisie.setBounds(new Rectangle(25, 23, 71, 21));
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
    this.setSize(new Dimension(304, 129));
    this.setTitle("Saisies & boutons - 1");
    txtSaisie.setBounds(new Rectangle(120, 21, 138, 24));
    cmdAfficher.setText("Afficher");
    cmdAfficher.setBounds(new Rectangle(111, 77, 77, 20));
    cmdAfficher.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdAfficher_actionPerformed(e);
      }
    });
    contentPane.add(lblSaisie, null);
    contentPane.add(txtSaisie, null);
    contentPane.add(cmdAfficher, null);
  }
   /**已替换,以便在窗口关闭时退出*/
  protected void processWindowEvent(WindowEvent e) {
    super.processWindowEvent(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.exit(0);
    }
  }
  void cmdAfficher_actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(this, "texte saisi="+txtSaisie.getText(), "Vérification de la saisie",JOptionPane.INFORMATION_MESSAGE);
  }
}

5.2.3.2.1. 属性
  JPanel contentPane;
  JLabel lblSaisie = new JLabel();
  JTextField txtSaisie = new JTextField();
  JButton cmdAfficher = new JButton();

这里放置了型号为JPanel的组件容器以及三个组件。

5.2.3.2.2. 制造商
   /**构建框架*/
  public interface2() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
    lblSaisie.setText("Saisie");
    lblSaisie.setBounds(new Rectangle(25, 23, 71, 21));
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
    this.setSize(new Dimension(304, 129));
    this.setTitle("Saisies & boutons - 1");
    txtSaisie.setBounds(new Rectangle(120, 21, 138, 24));
    cmdAfficher.setText("Afficher");
    cmdAfficher.setBounds(new Rectangle(111, 77, 77, 20));
    cmdAfficher.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdAfficher_actionPerformed(e);
      }
    });
    contentPane.add(lblSaisie, null);
    contentPane.add(txtSaisie, null);
    contentPane.add(cmdAfficher, null);
  }

interface2 生成器与之前研究的图形界面的生成器类似。区别在于 jbInit 方法中:窗口的生成代码取决于其中放置的组件。 我们可以复用 jbInit 的代码,并在其中添加自己的注释:

  private void jbInit() throws Exception  {
       // 窗口本身(大小、标题)
this.setSize(new Dimension(304, 129));
    this.setTitle("Saisies & boutons - 1");
         // 组件容器
    contentPane = (JPanel) this.getContentPane();
         // 该容器无格式管理器
    contentPane.setLayout(null);
         // 标签 lblSaisie(标签文字、位置、大小)
         lblSaisie.setText("Saisie");
    lblSaisie.setBounds(new Rectangle(25, 23, 71, 21));
        // 输入字段(位置、大小)
    txtSaisie.setBounds(new Rectangle(120, 21, 138, 24));
         // “显示”按钮(标签、位置、大小)
    cmdAfficher.setText("Afficher");
    cmdAfficher.setBounds(new Rectangle(111, 77, 77, 20));
         // 按钮的事件处理程序
    cmdAfficher.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdAfficher_actionPerformed(e);
      }
    });
         // 将 3 个组件添加到容器中
    contentPane.add(lblSaisie, null);
    contentPane.add(txtSaisie, null);
    contentPane.add(cmdAfficher, null);
  }//jbInit

需要注意两点:

  • 这段代码本可以手动编写。这意味着无需借助JBuilder即可构建图形用户界面。
  • 按钮 cmdAfficher 的事件处理程序的设置方式。组件 cmdAfficher 的事件处理程序本可以通过 cmdAfficher.addActionListener(new 处理程序()) 声明,其中 gestionnaire 是一个包含公共方法 actionPerformed 的类,该方法负责处理对按钮 Afficher 的点击。在此,JBuilder 使用了一个匿名类的实例作为处理程序:
 new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdAfficher_actionPerformed(e);
      }

随后创建了一个 ActionListener 接口的新实例,并随即定义了其 actionPerformed 方法。该方法仅调用 interface2 类中的一个方法。 这一切不过是一种技巧,旨在将该窗口组件的事件处理过程定义在与窗口相同的类中。也可以采用其他方式:

cmdAfficher.addActionListener(this)

这导致方法 actionPerformed 将被在 this 中查找,即在窗口类中查找。第二种方法看似更简单,但第一种方法具有一个优势:它允许为不同的按钮设置不同的处理程序,而第二种方法则无法做到这一点。 在后一种情况下,唯一的 actionPeformed 方法必须处理不同按钮的点击事件,因此必须先识别触发事件的按钮,然后才能开始处理。

5.2.3.2.3. 事件处理程序

这里涉及的是我们之前已经研究过的:

   /**已替换,这样当窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
    super.processWindowEvent(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.exit(0);
    }
  }

     // 点击“显示”按钮
  void cmdAfficher_actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(this, "texte saisi="+txtSaisie.getText(), "Vérification de la saisie",JOptionPane.INFORMATION_MESSAGE);
  }

5.2.3.3. Conclusion

从这两个研究项目中,我们可以得出结论:一旦使用JBuilder构建了图形用户界面,开发人员的工作就是编写用于管理该图形用户界面的事件处理程序。

5.2.4. 寻求帮助

使用 Java 时,我们经常需要帮助,这主要是因为可用的类数量非常庞大。这里我们提供一些关于如何查找类相关帮助的提示。请选择菜单中的“帮助/帮助主题”选项。

Image

帮助界面通常包含两个窗口:

  • 左侧窗口用于输入搜索内容,包含三个标签页:SommaireIndex Chercher
  • 右侧窗口显示搜索结果

关于如何使用 JBuilder 帮助功能,系统提供了相关指引。请在 JBuilder 帮助菜单中选择“帮助/使用帮助”选项。系统将向您说明如何使用帮助功能,例如会介绍帮助查看器的各个组成部分:

Image

让我们更仔细地查看页面 SommaireIndex

5.2.4.1. 帮助:目录

Image

5.2.4.1.1. 目录:Java 入门

这里不仅包含 Java 的基础知识,还涵盖了本选项中涉及的主题列表所示的其他内容:

Image

5.2.4.1.2. 目录:教程

若在上述目录中选择“教程”选项,右侧窗口将显示可用的教程列表:

Image

基础教程对于初次接触 JBuilder 的用户尤为有用。除了上述内容外,还有许多其他教程;当您想要开发应用程序时,不妨先查看是否有相关的教程可以提供帮助。

5.2.4.1.3. 目录:JDK

选择“Java 2 JDK 1.3”选项后,即可访问 JDK 中的所有库。 通常,如果需要查询特定类的信息且不清楚该类位于哪个库中,这里并不是最佳的搜索途径。不过,若您希望全面了解 Java 库的整体架构,此选项便颇具价值。

5.2.4.2. 帮助:索引

在帮助窗口的左侧,选择“索引”选项卡。例如,此选项可帮助您查找有关某个类的帮助信息。假设您想了解输入字段 swingJTextField 的方法。 在搜索文本输入框中输入 JTextField

Image

帮助功能将返回以您输入的文本开头的索引条目:

Image

接下来,您只需双击感兴趣的条目,此处为 JTextField 类。该类的帮助信息随即会在右侧窗口中显示:

Image

此时将为您提供该类的完整描述。

5.2.5. 一些 Swing 组件

接下来,我们将介绍一些涉及最常用swing组件的应用示例,以便了解其主要方法和属性。对于每个应用,我们将展示图形界面以及相关代码,特别是事件处理程序的代码。

5.2.5.1. JLabel 和 JTextField 组件

我们之前已经接触过这两个组件。JLabel 是一个文本组件,而 JTextField 是一个输入字段组件。它们的两个主要方法是

String getText()
用于获取输入字段的内容或标签文本
void setText(String unTexte)
用于将 unTexte 放入字段或标签中

通常用于 JTextField 的事件如下:

actionPerformed
表示用户已确认(按回车键)所输入的文本
caretUpdate
表示用户移动了输入光标
inputMethodChanged
表示用户修改了输入字段

以下是一个使用事件 caretUpdate 来跟踪输入字段变化的示例:

Image

编号
类型
名称
角色
1
JTextField
txtSaisie
输入字段
2
JTextField
txtControle
实时显示第1条的文本
3
JButton
cmdEffacer
用于清除字段 1 和 2
4
JButton
cmdQuitter
退出应用程序

该应用程序的相关代码如下:

import java.awt.*;
....


public class Cadre1 extends JFrame {
  JPanel contentPane;
  JTextField txtSaisie = new JTextField();
  JLabel jLabel1 = new JLabel();
  JLabel jLabel2 = new JLabel();
  JTextField txtControle = new JTextField();
  JButton CmdEffacer = new JButton();
  JButton CmdQuitter = new JButton();

  /**构建框架*/
  public Cadre1() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
    ....
    txtSaisie.addCaretListener(new javax.swing.event.CaretListener() {
      public void caretUpdate(CaretEvent e) {
        txtSaisie_caretUpdate(e);
      }
    });

...
    CmdEffacer.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        CmdEffacer_actionPerformed(e);
      }
    });
...
    CmdQuitter.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        CmdQuitter_actionPerformed(e);
      }
    });
....
}

   /**已替换,这样窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
...
  }

  void txtSaisie_caretUpdate(CaretEvent e) {
         // 输入光标已移动
    txtControle.setText(txtSaisie.getText());
  }

  void CmdQuitter_actionPerformed(ActionEvent e) {
        // 退出应用程序
    System.exit(0);
  }

  void CmdEffacer_actionPerformed(ActionEvent e) {
        // 清空输入字段内容
    txtSaisie.setText("");
  }
}

以下是一个执行示例:

Image

5.2.5.2. 组件 JComboBox

Image

Image

JComboBox 组件是一个带有输入框的下拉列表:用户既可以在 (2) 中选择一个项目,也可以在 (1) 中输入文本。默认情况下,JComboBox 组件不可编辑。 必须显式调用 setEditable(true) 方法才能使其可编辑。要了解 JComboBox 类,请在帮助索引中输入 JComboBox

JComboBox 对象可以通过多种方式创建:

new JComboBox()
创建一个空下拉列表
new JComboBox (Object[] items)
创建一个包含对象数组的下拉列表
new JComboBox(Vector items)
同上,但使用对象向量

人们可能会感到惊讶,下拉列表通常包含字符串,却也能包含对象。从视觉上来看,确实如此。如果一个 JComboBox 包含一个 obj 对象,它将显示字符串 obj.toString()。需要记住的是,每个对象都继承自 Object 类的 toString 方法,该方法会返回一个“代表”该对象的字符串。

JCombobox 类的有用方法如下:

void addItem(Object unObjet)
向下拉列表中添加一个对象
int getItemCount()
返回下拉列表中的项目数量
Object getItemAt(int i)
返回下拉列表中的第 i 个对象
void insertItemAt(Object unObjet, int i)
将 unObjet 插入下拉列表的第 i 个位置
int getSelectedIndex()
返回下拉列表中选中项的编号
Object getSelectedItem()
返回下拉列表中选中的对象
void setSelectedIndex(int i)
选择下拉列表中的第 i 个项目
void setSelectedItem(Object unObjet)
在组合框中选中指定的对象
void removeAllItems()
清空下拉列表
void removeItemAt(int i)
从下拉列表中移除第 i 个项目
void removeItem(Object unObjet)
从下拉列表中移除指定的对象
void setEditable(boolean val)
将下拉列表设为可编辑(val=true)或不可编辑(val=false)

在下拉列表中选择项目时,会触发事件 actionPerformed,该事件可用于监听下拉列表中选中的项目发生变化。在下面的应用程序中,我们利用此事件来显示列表中选中的项目。

Image

此处仅展示窗口的相关代码。

public class Cadre1 extends JFrame {
  JPanel contentPane;
  JComboBox jComboBox1 = new JComboBox();
  JLabel jLabel1 = new JLabel();

   /**构建框架*/
  public Cadre1() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // 处理 - 填充下拉列表
    String[] infos={"un","deux","trois","quatre"};
    for(int i=0;i<infos.length;i++)
      jComboBox1.addItem(infos[i]);
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
        ....

    jComboBox1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        jComboBox1_actionPerformed(e);
      }
    });
       .... 
  }

  void jComboBox1_actionPerformed(ActionEvent e) {
        // 已选中新元素 - 显示该元素
    JOptionPane.showMessageDialog(this,jComboBox1.getSelectedItem(),
"actionPerformed",JOptionPane.INFORMATION_MESSAGE);
  }

}

5.2.5.3. 组件 JList

Swing 组件 JList 比其 awt 库中的对应组件更为复杂。主要存在两点差异:

  • 列表的内容由一个与列表本身不同的对象管理。 此处我们将使用 DefaultListModel 对象,该对象的使用方式与 Vector 相同,但额外会在其内容发生变化时立即通知 JList 对象,从而使列表的视觉显示随之更新。
  • 列表默认不支持滚动。必须将列表放入一个名为 ScrollPane 的容器中,该容器才支持滚动功能。

在源代码中,列表的定义可以如下所示:

      // 列表值的向量 
DefaultlistModel valeurs=new DefaultListModel();
// 列表本身及其关联的值向量
  JList jList1 = new JList(valeurs);
     // 用于放置列表以实现滚动列表功能的滚动容器
  JScrollPane jScrollPane1 = new JScrollPane(jList1);

为了将列表 jList1 包含在容器 jScrollPane1 中,由 JBuilder 生成的代码采用了不同的处理方式:

  • 在窗口属性中声明容器
  JScrollPane jScrollPane1 = new JScrollPane();
  • 随后在 jbInit 的代码中,将列表与容器关联
    jScrollPane1.getViewport().add(jList1, null);

要向上述列表 JList1 添加值,只需将其添加到其值向量 valeurs 中:

    // 初始化列表
    for(int i=0;i<10;i++)
      valeurs.addElement(""+i);

随后将显示如下窗口:

Image

该界面是如何构建的?

  • 在组件的“Swing容器”页面中选择一个JScrollPane组件,将其拖放到窗口中并调整至所需尺寸
  • 在组件的“Swing”页面中选择一个 JList 组件,将其拖放到 JScrollPane 容器中,该组件将占据整个容器空间。

JBuilder生成的代码需要稍作修改,以获得以下代码:

public class interfaceAppli extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  DefaultListModel valeurs=new DefaultListModel();
  JList jList1 = new JList(valeurs);
  JScrollPane jScrollPane1 = new JScrollPane();
  JLabel jLabel2 = new JLabel();


   /**构建框架*/
  public interfaceAppli() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // 处理
     // 将列表包含在 scrollPane 中
    // 初始化列表
    for(int i=0;i<10;i++)
      valeurs.addElement(""+i);
  }

   /**初始化组件*/
  private void jbInit() throws Exception  {
        ....
         // 列表 jList1 与容器 jcrollPane1 相关联
    jScrollPane1.getViewport().add(jList1, null);
  }
   /**已替换,因此窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
        ....
  }
}

现在,让我们通过在帮助索引中查找 JList,来了解 JList 类的主要方法。JList 对象可以通过多种方式构建:

Image

我们采用的方法是一种简单的方法:创建一个空的 DefaultListModel V,然后通过 new JList(V) 将其与要创建的列表关联起来。 列表的内容并非由 JList 对象管理,而是由包含列表值的对象管理。 如果列表内容是通过基于 Vector 类的 DefaultListModel 对象构建的,则可以使用 Vector 类的方法来向列表中添加、插入和删除元素。 列表可以是单选或多选。这由方法 setSelectionMode 确定:

Image

可通过 getSelectionMode 查询当前的选中模式:

Image

可通过以下方法获取所选元素:

Image

我们知道可以通过构造函数 JList(DefaultListModel) 将一个值向量与列表关联。反之,我们可以通过以下方式从列表 JList 获取对象 DefaultListModel

Image

现在我们掌握的知识已经足够编写以下应用程序:

Image

该窗口的组件如下:

编号
类型
名称
作用
1
JTextField
txtSaisie
输入字段
2
JList
jList1
容器中的列表jScrollPane1
3
JList
jList2
包含在容器中的列表 jScrollPane2
4
JButton
cmd1To2
将列表 1 中选中的项目转移到列表 2
5
JButton
cmd2To1
执行相反操作
6
JButton
cmdRaz1
清空列表 1
7
JButton
cmdRaz2
清空列表 2

用户在字段 (1) 中输入文本并提交。此时,输入字段将触发事件 actionPerformed,该事件用于将输入的文本添加到列表 1 中。以下是此第一项功能所需的代码:

public class interfaceAppli extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JLabel jLabel2 = new JLabel();
  JLabel jLabel3 = new JLabel();
  JTextField txtSaisie = new JTextField();
  JButton cmd1To2 = new JButton();
  JButton cmd2To1 = new JButton();
  DefaultListModel v1=new DefaultListModel();
  DefaultListModel v2=new DefaultListModel();
  JList jList1 = new JList(v1);
  JList jList2 = new JList(v2);
  JScrollPane jScrollPane1 = new JScrollPane();
  JScrollPane jScrollPane2 = new JScrollPane();
  JButton cmdRaz1 = new JButton();
  JButton cmdRaz2 = new JButton();
  JLabel jLabel4 = new JLabel();

   /**构建框架*/
  public interfaceAppli() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }//interfaceAppli

   /**初始化组件*/
  private void jbInit() throws Exception  {
    ...
    txtSaisie.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        txtSaisie_actionPerformed(e);
      }
    });
        ...
         // Jlist1 被放置在容器中 jScrollPane1
    jScrollPane1.getViewport().add(jList1, null);
        // Jlist2 被放置在容器中 jScrollPane2
    jScrollPane2.getViewport().add(jList2, null);
        ...
  }
   /**已替换,这样当窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
...
  }

  void txtSaisie_actionPerformed(ActionEvent e) {
     // 输入的文本已通过验证
     // 提取文本时去除了首尾空格
    String texte=txtSaisie.getText().trim();
     // 如果为空,则不采用
    if(texte.equals("")){
      // 错误信息
      JOptionPane.showMessageDialog(this,"Vous devez taper un texte",
        "Erreur",JOptionPane.WARNING_MESSAGE);
      // 结束
      return;
    }//if
    // 如果非空,将其添加到列表1的值中
    v1.addElement(texte);
     // 并清空输入字段
    txtSaisie.setText("");
  }/// txtSaisie_actionperformed
}//分类

将选定项目从一个列表转移到另一个列表的代码如下:

  void cmd1To2_actionPerformed(ActionEvent e) {
    // 将列表1中选中的项目转移到列表2
    transfert(jList1,jList2);
  }//cmd1To2

  void cmd2To1_actionPerformed(ActionEvent e) {
    // 将 jList2 中选中的项目转移到 jList1
      transfert(jList2,jList1);
  }//cmd2TO1

  private void transfert(JList L1, JList L2){
      // 将列表 1 中选中的元素转移到列表 2
       // 从 L1 中获取所选元素的索引数组
      int[] indices=L1.getSelectedIndices();
      // 需要做些什么?
      if (indices.length==0) return;
      // 从 L1 中获取值
      DefaultListModel v1=(DefaultListModel)L1.getModel();
      // 以及 L2 中的值
      DefaultListModel v2=(DefaultListModel)L2.getModel();
      for(int i=indices.length-1;i>=0;i--){
        // 将 L1 中选定的值添加到 L2
        v2.addElement(v1.elementAt(indices[i]));
        // 从 L1 复制到 L2 的元素必须从 L1 中删除
        v1.removeElementAt(indices[i]);
      }//用于
  }//转移

与按钮 Raz 关联的代码非常简单:

  void cmdRaz1_actionPerformed(ActionEvent e) {
    // 清空列表 1
    v1.removeAllElements();
  }//cmd Raz1

  void cmdRaz2_actionPerformed(ActionEvent e) {
    // 清空列表 2
    v2.removeAllElements();
  }///cmd Raz2

5.2.5.4. 复选框 JCheckBox,单选按钮 JButtonRadio

我们打算编写以下应用程序:

Image

窗口的组件如下:

编号
类型
名称
作用
1
JButtonRadio
jButtonRadio1
jButtonRadio2
jButtonRadio3
属于组 buttonGroup1 的 3 个单选按钮
2
JCheckBox
jCheckBox1
jCheckBox2
jCheckBox3
3 个复选框
3
JList
jList1
一个列表位于一个容器中jScrollPane1
4
ButtonGroup
buttonGroup1
不可见组件——用于将3个单选按钮分组,以便其中一个被选中时,其余的会自动取消选中。

单选按钮组可以按以下方式构建:

  • 将每个单选按钮放置到位,无需考虑分组
  • 在容器中放置一个名为 ButtonGroup 的 Swing 组件。该组件为非可见组件,因此不会在窗口设计器中显示,但在其结构中可见:

Image

上图中,在 Autre 分支下可见该窗口的非视觉属性。创建完单选按钮组后,即可将各个单选按钮与该组关联。为此,请选择单选按钮的属性:

Image

并在单选按钮的 buttonGroup 属性中,输入希望将该单选按钮归入的组名称,此处为 buttonGroup1。对 3 个单选按钮重复此操作。

单选按钮和复选框的主要方法是 isSelected(),该方法用于指示复选框或单选按钮是否被选中。可通过 getText() 获取组件关联的文本,并通过 setText(String unTexte)。可通过方法 setSelected(boolean value) 选中复选框/单选按钮。

当单击单选按钮或复选框时,将触发事件 actionPerformed。在下面的代码中,我们利用该事件来跟踪单选按钮和复选框的值变化:

public class interfaceAppli extends JFrame {
  JPanel contentPane;
  JRadioButton jRadioButton1 = new JRadioButton();
  JRadioButton jRadioButton2 = new JRadioButton();
  JRadioButton jRadioButton3 = new JRadioButton();
  JCheckBox jCheckBox1 = new JCheckBox();
  JCheckBox jCheckBox2 = new JCheckBox();
  JCheckBox jCheckBox3 = new JCheckBox();
  ButtonGroup buttonGroup1 = new ButtonGroup();
  JScrollPane jScrollPane1 = new JScrollPane();
  DefaultListModel valeurs=new DefaultListModel();
  JList jList1 = new JList(valeurs);

   /**构建框架*/
  public interfaceAppli() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
    jRadioButton1.setSelected(true);
    jRadioButton1.setText("un");
    jRadioButton1.setBounds(new Rectangle(57, 31, 49, 23));
    jRadioButton1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        afficheRadioButtons(e);
      }
    });

    jRadioButton2.setBounds(new Rectangle(113, 30, 49, 23));
    jRadioButton2.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        afficheRadioButtons(e);
      }
    });
    jRadioButton2.setText("deux");

    jRadioButton3.setBounds(new Rectangle(168, 30, 49, 23));
    jRadioButton3.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        afficheRadioButtons(e);
      }
    });
    jRadioButton3.setText("trois");

// 单选按钮已分组
    buttonGroup1.add(jRadioButton1);
    buttonGroup1.add(jRadioButton2);
    buttonGroup1.add(jRadioButton3);

// 复选框
    jCheckBox1.setText("A");
    jCheckBox1.setBounds(new Rectangle(58, 69, 32, 17));
    jCheckBox1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        afficheCases(e);
      }
    });

    jCheckBox2.setBounds(new Rectangle(112, 69, 40, 17));
    jCheckBox2.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        afficheCases(e);
      }
    });
    jCheckBox2.setText("B");

    jCheckBox3.setText("C");
    jCheckBox3.setBounds(new Rectangle(170, 69, 37, 17));
    jCheckBox3.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        afficheCases(e);
      }
    });

    ....
  }
   /**已替换,这样窗口关闭时即可退出*/
  protected void processWindowEvent(WindowEvent e) {
        ...
  }

  private void afficheRadioButtons(ActionEvent e){
     // 显示 3 个单选按钮的值
    valeurs.addElement("boutons radio=("+jRadioButton1.isSelected()+","+
      jRadioButton2.isSelected()+","+jRadioButton3.isSelected()+")");
  }//afficheRadioButtons

  void afficheCases(ActionEvent e) {
     // 显示复选框的值
    valeurs.addElement("cases à cocher=["+jCheckBox1.isSelected()+","+
      jCheckBox2.isSelected()+","+jCheckBox3.isSelected()+")");
  }//afficheCases

}//分类

以下是一个执行示例:

Image

5.2.5.5. 组件 JScrollBar

让我们实现以下应用程序:

Image

编号
类型
名称
角色
1
JScrollBar
jScrollBar1
一台水平变速器
2
JScrollBar
jScrollBar2
一台立式变频器
3
JTextField
txtvaleurHS
显示水平调节器1的数值——也可用于锁定该数值
4
JTextField
txtvaleurVS
显示垂直滑块 2 的数值 - 也可用于锁定该数值
  • JScrollBar 旋钮允许用户在整数值范围内选择一个数值,该范围由旋钮上的“刻度带”表示,光标会在其上移动。
  • 对于水平滑块,左端代表范围的最小值,右端代表最大值,光标代表当前选定的值。对于垂直滑块,最小值由顶端表示,最大值由底端表示。默认 (min,max) 对为 (0,100)。
  • 点击滑块两端会使数值按点击端(默认值为1)的增量(正或负)发生变化,该增量参数名为unitIncrement
  • 在滑块两侧点击会使数值按点击端(称为 blockIncrement,默认值为 10)的方向增加或减少一个增量。
  • 可以通过方法 getMinimum()、getMaximum()、 getValue()、getUnitIncrement()、getBlockIncrement() 获取,这些方法均返回一个整数,并可通过方法 setMinimum(int min)、 setMaximum(int max)、setValue(int val)、setUnitIncrement(int uInc)、 setBlockIncrement(int bInc)

在使用 JScrollBar 组件时,有几点需要注意。首先,该组件位于 Swing 组件栏中:

Image

将其拖放到容器上时,默认方向为垂直。可通过以下 orientation 属性将其设置为水平:

Image

在上方的属性表中,我们可以看到可以访问以下属性:minimummaximumvalueunitIncrementblockIncrement。因此可以在设计时对其进行设置。当在容器上放置滚动条时,其“变化条”不会显示:

Image

可以通过为组件添加边框来解决此问题。这可通过其 border 属性实现,该属性可取不同值:

Image

例如,设置 RaisedBevel 后的效果如下:

Image

当点击垂直滑块的顶端时,其数值会减小。这可能会让普通用户感到意外,因为他们通常期望数值“上升”。通过将 unitIncrementblockIncrement 设置为负值,即可解决此问题。

如何跟踪滑块的变化?当滑块的数值发生变化时,会触发事件 adjustmentValueChanged。只需将一个处理程序与该事件关联,即可在滑块数值每次变化时收到通知。

Image

本应用程序的实用代码如下:

....

public class cadreAppli extends JFrame {
  JPanel contentPane;
  JScrollBar jScrollBar1 = new JScrollBar();
  Border border1;
  JTextField txtValeurHS = new JTextField();
  JScrollBar jScrollBar2 = new JScrollBar();
  JTextField txtValeurVS = new JTextField();
  TitledBorder titledBorder1;

   /**构建框架*/
  public cadreAppli() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
...

// 为滚动条添加边框
    border1 = BorderFactory.createBevelBorder(BevelBorder.RAISED,Color.white,Color.white,new Color(134, 134, 134),new Color(93, 93, 93));
        // 边框上不显示标题
    titledBorder1 = new TitledBorder("");

    jScrollBar1.setOrientation(JScrollBar.HORIZONTAL);
    jScrollBar1.setBorder(BorderFactory.createRaisedBevelBorder());
    jScrollBar1.setAutoscrolls(true);
    jScrollBar1.setBounds(new Rectangle(37, 17, 218, 20));
    jScrollBar1.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
      public void adjustmentValueChanged(AdjustmentEvent e) {
        jScrollBar1_adjustmentValueChanged(e);
      }
    });

    txtValeurHS.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        txtValeurHS_actionPerformed(e);
      }
    });

    jScrollBar2.setBounds(new Rectangle(39, 51, 30, 27));
    jScrollBar2.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
      public void adjustmentValueChanged(AdjustmentEvent e) {
        jScrollBar2_adjustmentValueChanged(e);
      }
    });
    jScrollBar2.setAutoscrolls(true);
    jScrollBar2.setUnitIncrement(-1);
    jScrollBar2.setBorder(BorderFactory.createRaisedBevelBorder());

    txtValeurVS.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        txtValeurVS_actionPerformed(e);
      }
    });

    ......
  }

   /**已替换,这样窗口关闭时即可退出*/
  protected void processWindowEvent(WindowEvent e) {
...
  }

  void jScrollBar1_adjustmentValueChanged(AdjustmentEvent e) {
    // 滚动条 1 的值已更改
    txtValeurHS.setText(""+jScrollBar1.getValue());
  }

  void jScrollBar2_adjustmentValueChanged(AdjustmentEvent e) {
    // 滚动条 2 的值已更改
    txtValeurVS.setText(""+jScrollBar2.getValue());
  }

  void txtValeurHS_actionPerformed(ActionEvent e) {
    // 固定水平滚动条的值
    setValeur(jScrollBar1,txtValeurHS);
  }

  void txtValeurVS_actionPerformed(ActionEvent e) {
    // 固定垂直滚动条的值
    setValeur(jScrollBar2,txtValeurVS);
  }

  private void setValeur(JScrollBar jS, JTextField jT){
     // 将滚动条 jS 的值设置为字段 jT 中的文本
    int valeur=0;
    try{
      valeur=Integer.parseInt(jT.getText());
      jS.setValue(valeur);
    }
    catch (Exception e){
      // 显示错误
      afficher(""+e);
    }//try-catch
  }//setValeur

  void afficher(String message){
    // 在对话框中显示一条消息
    JOptionPane.showMessageDialog(this,message,"Menus",JOptionPane.INFORMATION_MESSAGE);
  }//显示

}

以下是一个执行示例:

Image

5.2.5.6. 组件 JTextArea

组件 JTextArea 允许输入多行文本,而组件 JTextField 仅支持输入单行文本。 若将该组件放置于滚动容器(JScrollPane)中,即可形成滚动文本输入框。此类组件常见于电子邮件应用程序中,用户可在 JTextArea 组件中输入待发送的邮件正文。 常用方法包括:String getText() 用于获取文本框的内容,setText(String unTexte) 用于将 unTexte 放入文本框,append(String unTexte) 用于将 unTexte 追加到文本框中已有的文本后。考虑以下应用程序:

Image

编号
类型
名称
角色
1
JTextArea
txtTexte
多行文本框
2
JButton
cmdAfficher
在对话框中显示 1 的内容
3
JButton
cmdEffacer
清空 1 的内容
4
JTextField
txtAjout
按下回车键后,文本将添加到文本 1 中。
5
JScrollPane
jScrollPane1
一个滚动容器,其中放置了文本框 1,以实现文本框的滚动效果。

相关代码如下:

.....

public class cadreAppli extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JButton cmdAfficher = new JButton();
  JScrollPane jScrollPane1 = new JScrollPane();
  JTextArea txtTexte = new JTextArea();
  JLabel jLabel2 = new JLabel();
  JTextField txtAjout = new JTextField();
  JButton cmdEffacer = new JButton();

  /**构建框架*/
  public cadreAppli() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {

    cmdAfficher.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdAfficher_actionPerformed(e);
      }
    });
    txtAjout.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        txtAjout_actionPerformed(e);
      }
    });
    cmdEffacer.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdEffacer_actionPerformed(e);
      }
    });
    .......
    jScrollPane1.getViewport().add(txtTexte, null);
  }
   /**已替换,因此窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
        ........
  }

  void cmdAfficher_actionPerformed(ActionEvent e) {
    // 显示 TextArea 的内容
    afficher(txtTexte.getText());
  }

    void afficher(String message){
     // 在对话框中显示一条消息
    JOptionPane.showMessageDialog(this,message,"Suivi",JOptionPane.INFORMATION_MESSAGE);
  }// 显示

  void cmdEffacer_actionPerformed(ActionEvent e) {
    txtTexte.setText("");
  }//cmdEffacer_actionPerformed

  void txtAjout_actionPerformed(ActionEvent e) {
    // 添加文本
    txtTexte.append(txtAjout.getText());
     // 清空并添加
    txtAjout.setText("");
  }//
}

5.2.6. 鼠标事件

在容器中绘图时,了解鼠标的位置非常重要,例如在点击时显示一个点。鼠标的移动会在其移动的容器中触发事件。以下是 JBuilder 为容器 JPanel 提供的事件示例:

Image

mouseClicked
鼠标点击
mouseDragged
鼠标移动,左键按下
mouseEntered
鼠标刚进入容器区域
mouseExited
鼠标刚离开容器区域
mouseMoved
鼠标正在移动
mousePressed
点击鼠标左键
mouseReleased
释放鼠标左键

这是一个有助于更好地理解各种鼠标事件何时发生的程序:

Image

编号
类型
名称
角色
1
JTextField
txtPosition
用于显示容器中的鼠标位置(如有 MouseMoved)
2
JList
lstAffichage
用于显示除 MouseMoved 以外的鼠标事件
3
JButton
cmdEffacer
用于清除 2 的内容

运行该程序时,单击鼠标会得到以下结果:

Image

事件在列表中按从上到下的顺序堆叠。因此,上图显示点击会触发三个事件,顺序为:

  1. 按下按钮时为
  2. MouseReleased 松开按钮时
  3. MouseClicked 表示前两个事件的连续发生被视为一次点击。这可能是双击。但上文中的信息 clickCount=1 表明这只是一次单击。

现在,如果按下按钮、移动鼠标并松开按钮:

Image

这里可以看到三个事件:

  1. MousePressed:初次按下按钮时
  2. MouseDragged:在按住按钮的同时移动鼠标
  3. MouseReleased 松开按钮时

在上述两个示例中,我们可以看到鼠标事件会携带各种信息,其中包括鼠标的坐标(x,y),例如上文第一行中的 (408,65)。

继续分析可发现,当鼠标离开容器或移至容器中的某个组件上时,会触发事件 MouseExited。 在后一种情况下,容器会收到事件 MouseExited,而组件会收到事件 MouseEntered。当鼠标离开组件并返回容器时,情况将相反。

双击时会发生什么?

Image

其事件与单击完全相同。只是该事件携带了 clickCount=2 的信息(参见上文),表明实际上发生了双击。

该应用程序的实用代码如下:

public class Cadre1 extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JTextField txtPosition = new JTextField();
  JScrollPane jScrollPane1 = new JScrollPane();
  DefaultListModel valeurs=new DefaultListModel();
  JList lstAffichage = new JList(valeurs);
  JButton cmdEffacer = new JButton();

  /**构建框架*/
  public Cadre1() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {

    contentPane.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
      public void mouseMoved(MouseEvent e) {
        contentPane_mouseMoved(e);
      }
      public void mouseDragged(MouseEvent e) {
        contentPane_mouseDragged(e);
      }
    });

    contentPane.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseEntered(MouseEvent e) {
        contentPane_mouseEntered(e);
      }
      public void mouseExited(MouseEvent e) {
        contentPane_mouseExited(e);
      }
      public void mousePressed(MouseEvent e) {
        contentPane_mousePressed(e);
      }
      public void mouseClicked(MouseEvent e) {
        contentPane_mouseClicked(e);
      }
      public void mouseReleased(MouseEvent e) {
        contentPane_mouseReleased(e);
      }
    });

    cmdEffacer.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cmdEffacer_actionPerformed(e);
      }
    });

    jScrollPane1.getViewport().add(lstAffichage, null);

...............
  }
   /**已替换,这样当窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
    .........
  }

  void contentPane_mouseMoved(MouseEvent e) {
    txtPosition.setText("("+e.getX()+","+e.getY()+")");
  }

  void contentPane_mouseDragged(MouseEvent e) {
    afficher(e);
  }

  void contentPane_mouseEntered(MouseEvent e) {
    afficher(e);
  }

  void afficher(MouseEvent e){
     // 将事件显示在列表中 lstAffichage
    valeurs.insertElementAt(e,0);
  }

  void contentPane_mouseExited(MouseEvent e) {
    afficher(e);
  }

  void contentPane_mousePressed(MouseEvent e) {
    afficher(e);
  }

  void contentPane_mouseClicked(MouseEvent e) {
    afficher(e);
  }

  void contentPane_mouseReleased(MouseEvent e) {
    afficher(e);
  }

  void cmdEffacer_actionPerformed(ActionEvent e) {
     // 清空列表
    valeurs.removeAllElements();
  }
}

5.2.7. 创建带菜单的窗口

现在让我们看看如何使用 JBuilder 创建带菜单的窗口。我们将创建以下窗口:

Image

Image

创建一个新项目,初始状态为一个空窗口。在“Swing 容器”组件列表中选择 JMenuBar 组件(参见下图 1),并将它拖放到正在设计的窗口上。

Image

设计窗口中不会显示任何内容,但组件 JMenuBar 会出现在窗口的结构面板中:

Image

双击上方的 jMenuBar1 元素,即可访问设计模式菜单:

Image

1
插入菜单项
2
插入分隔符
3
插入嵌套菜单
4
删除菜单项
5
禁用菜单项
6
需勾选的菜单项
7
反选单选按钮

要创建您的第一个菜单项,请在上面的 A 框中输入“选项 A”,然后在下方按顺序输入:A1、A2、分隔符、A3、 A4。

Image

然后在旁边:

Image

使用工具 3 标注 B3 为嵌套子菜单。

随着菜单设计的推进,窗口的逻辑结构也在不断演变:

Image

如果现在运行我们的应用程序,我们会看到一个没有菜单的空白窗口。我们需要将创建的菜单与窗口关联起来。为此,请在窗口结构中选择对象 this

Image

此时,您可以访问 this 的属性:

Image

其中一项属性是 JMenuBar,用于确定将与该窗口关联的菜单。点击 JMenuBar 右侧的单元格。此时将显示所有已创建的菜单供您选择。 此处仅显示 jMenuBar1。请选中它。

运行应用程序(F9):

Image

现在,我们有了一个菜单,但这些选项目前尚无作用。菜单选项被视为组件:它们具有属性与事件。在菜单结构中,选择选项 jMenuItem1

Image

此时,您可以访问其属性和事件:

Image

选择“事件”页面,并点击事件 actionPerformed 右侧的单元格:这是点击菜单项时触发的事件。系统默认提供了一个处理过程。双击该过程以查看其代码:

Image

我们将编写以下简单代码:

  void jMenuItem1_actionPerformed(ActionEvent e) {
    afficher("L'option A1 a été sélectionnée");
  }

  void afficher(String message){
     // 在对话框中显示一条消息
    JOptionPane.showMessageDialog(this,message,"Menus",JOptionPane.INFORMATION_MESSAGE);
  }//显示

运行应用程序并选择选项 A1,将显示以下消息:

Image

该应用程序的有效代码如下:

.....

public class Cadre1 extends JFrame {
  JPanel contentPane;
  BorderLayout borderLayout1 = new BorderLayout();
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem jMenuItem1 = new JMenuItem();
  JMenuItem jMenuItem2 = new JMenuItem();
  JMenuItem jMenuItem3 = new JMenuItem();
  JMenuItem jMenuItem4 = new JMenuItem();
  JMenu jMenu2 = new JMenu();
  JMenuItem jMenuItem5 = new JMenuItem();
  JMenuItem jMenuItem6 = new JMenuItem();
  JMenu jMenu3 = new JMenu();
  JMenuItem jMenuItem7 = new JMenuItem();
  JMenuItem jMenuItem8 = new JMenuItem();
  JMenuItem jMenuItem9 = new JMenuItem();

  /**构建框架*/
  public Cadre1() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
   /**初始化组件*/
  private void jbInit() throws Exception  {
        // 窗口与菜单相关联
    this.setJMenuBar(jMenuBar1);

    jMenu1.setText("Options A");
    jMenuItem1.setText("A1");
    jMenuItem1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        jMenuItem1_actionPerformed(e);
      }
    });
    jMenuItem2.setText("A2");
    jMenuItem3.setText("A3");
    jMenuItem4.setText("A4");
    jMenu2.setText("Options B");
    jMenuItem5.setText("B1");
    jMenuItem6.setText("B2");
    jMenu3.setText("B3");
    jMenuItem7.setText("B31");
    jMenuItem8.setText("B32");
    jMenuItem9.setText("B4");
    jMenuBar1.add(jMenu1);
    jMenuBar1.add(jMenu2);
    jMenu1.add(jMenuItem1);
    jMenu1.add(jMenuItem2);
    jMenu1.addSeparator();
    jMenu1.add(jMenuItem3);
    jMenu1.add(jMenuItem4);
    jMenu2.add(jMenuItem5);
    jMenu2.add(jMenuItem6);
    jMenu2.add(jMenu3);
    jMenu2.add(jMenuItem9);
    jMenu3.add(jMenuItem7);
    jMenu3.add(jMenuItem8);
  }
   /**已替换,因此窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
....
  }

  void jMenuItem1_actionPerformed(ActionEvent e) {
    afficher("L'option A1 a été sélectionnée");
  }

  void afficher(String message){
     // 在对话框中显示一条消息
    JOptionPane.showMessageDialog(this,message,"Menus",JOptionPane.INFORMATION_MESSAGE);
  }//显示

}

5.3. 对话框

5.3.1. 消息框

我们之前已经使用过类 JOptionPane 来显示消息。因此,以下代码:

import javax.swing.*;

public class dialog1 {
  public static void main(String arg[]){
    JOptionPane.showMessageDialog(null,"Un message","Titre de la boîte",JOptionPane.INFORMATION_MESSAGE);
  }
}

生成以下框的显示:

Image

关闭此窗口后,窗口虽消失,但其运行的执行线程并未停止。这种情况通常不会发生。对话框通常用于应用程序中,而该应用程序会在某个时刻使用 System.exit(n) 指令来停止所有线程。 在后续基于相同模型构建的示例中,我们将再次提及这一点。在 DOS 环境下,可通过 Ctrl-C 中断应用程序。对于 JBuilder,则需使用“执行/重置程序”选项(Ctrl-F2)。 此外,此处 showMessageDialog 的第一个参数是 null。通常情况并非如此,而是 this,其中 this 代表应用程序的主窗口。

5.3.2. 外观与感觉

上图所示的对话框外观可能会有所不同。可以通过类 javax.swing.UIManager 来设置这种外观。当我们注释 JBuilder 为第一个窗口生成的代码时,遇到了一条我们当时未加关注的语句:

      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

UIManager类(UI=用户界面)中的setLookAndFeel方法可用于设定图形界面的外观。 UIManager类提供了一个方法,用于查询界面可能的“外观”:

static UIManager.LookAndFeelInfo[]
getInstalledLookAndFeels()

该方法返回一个由 LookAndFeelInfo 类型元素组成的数组。该类有一个方法:

String
getClassName()

该方法返回“实现”给定外观的类名。让我们尝试以下程序:

import javax.swing.*;

public class LookAndFeels {
  // 显示可用的界面样式
  public static void main(String[] args) {
    // 列出已安装的界面样式
    UIManager.LookAndFeelInfo[] lf=UIManager.getInstalledLookAndFeels();
    // 显示
    for(int i=0;i<lf.length;i++){
      System.out.println(lf[i].getClassName());
    }//用于
  }//主
}//分类

它给出了以下结果:

javax.swing.plaf.metal.MetalLookAndFeel
com.sun.java.swing.plaf.motif.MotifLookAndFeel
com.sun.java.swing.plaf.windows.WindowsLookAndFeel

因此似乎有三种“不同的外观”。让我们回到消息显示程序,尝试各种可能的外观:

import javax.swing.*;

public class LookAndFeel2 {
  // 显示可用的界面样式
  public static void main(String[] args) {
    // 已安装的界面样式列表
    UIManager.LookAndFeelInfo[] lf=UIManager.getInstalledLookAndFeels();
    // 显示
    for(int i=0;i<lf.length;i++){
      // 外观管理器
      try{
        UIManager.setLookAndFeel(lf[i].getClassName());
      }catch(Exception ex){
        System.err.println(ex.getMessage());
      }//捕获
      // 消息
      JOptionPane.showMessageDialog(null,"Un message",lf[i].getClassName(),JOptionPane.INFORMATION_MESSAGE);
    }//for
  }//main
}//类

执行后显示如下:

从右到左分别对应“金属”、“图案”和“Windows”三种外观。

5.3.3. 确认框

JOptionPane 提供了一个方法 showConfirmDialog,用于显示带有按钮 OuiNonAnnuler。该类包含多个重载的 showConfirmDialog 方法。我们研究其中一个:

static int
showConfirmDialog(Component parentComponent, Object message, String title, int optionType)
parentComponent
对话框的父组件。通常为窗口或 null 值
message
要显示的消息
title
对话框的标题
optionType
JOptionPane.YES_NO_OPTION:是、否按钮
JOptionPane.YES_NO_CANCEL_OPTION:是、否、取消按钮

该方法返回的结果为:

JOptionPane.YES_OPTION
用户点击了“是”
JOptionPane.NO_OPTION
用户点击了“否”
JOptionPane.CANCEL_OPTION
用户点击了“取消”
JOptionPane.CLOSED_OPTION
用户关闭了对话框

以下是一个示例:

import javax.swing.*;

public class confirm1 {
  public static void main(String[] args) {
    // 显示确认框
    int réponse;
    affiche(JOptionPane.showConfirmDialog(null,"Voulez-vous continuer ?","Confirmation",JOptionPane.YES_NO_OPTION));
    affiche(JOptionPane.showConfirmDialog(null,"Voulez-vous continuer ?","Confirmation",JOptionPane.YES_NO_CANCEL_OPTION));
  }//主程序

  private static void affiche(int réponse){
     // 显示收到的响应类型
    switch(réponse){
      case JOptionPane.YES_OPTION :
        System.out.println("Oui");
        break;
      case JOptionPane.NO_OPTION :
        System.out.println("Non");
        break;
      case JOptionPane.CANCEL_OPTION :
        System.out.println("Annuler");
        break;
      case JOptionPane.CLOSED_OPTION :
        System.out.println("Fermeture");
        break;
    }//开关
  }//显示
}//分类

在控制台上,会显示消息 NonAnnuler

5.3.4. 输入框

JOptionPane 还支持使用方法 showInputDialog 进行数据输入。同样,这里也存在多个重载方法。我们介绍其中之一:

static String
showInputDialog(Component parentComponent, Object message, String title, int messageType)

这些参数我们已经多次遇到过。该方法返回用户输入的字符串。以下是一个示例:

import javax.swing.*;

public class input1 {
  public static void main(String[] args) {
    // 录入
    System.out.println("Chaîne saisie ["+
      JOptionPane.showInputDialog(null,"Quel est votre nom","Saisie du nom",JOptionPane.QUESTION_MESSAGE )
      + "]");
  }//手动
}//分类

输入框的显示:

Image

控制台显示:

Chaîne saisie [dupont]

5.4. 下拉列表框

现在,我们将关注 Java 2 中预定义的若干下拉框:

JFileChooser
用于在文件树中选择文件的下拉框
JColorChooser
用于选择颜色的下拉框

5.4.1. 下拉框 JFileChooser

我们将构建以下应用程序:

Image

控件如下:

编号
类型
名称
作用
0
JScrollPane
jScrollPane1
文本框 1 的滚动容器
1
JTextArea 本身位于 JScrollPane 中
txtTexte
用户输入的文本或从文件加载的文本
2
JButton
btnSauvegarder
用于将第1项的文本保存到文本文件中
3
JButton
btnCharger
用于将文本文件的内容加载到 1 中
4
JButton
btnEffacer
清空 1 的内容

使用非可视控件:jFileChooser1。该控件取自 JBuilder 的 Swing 容器面板:

Image

我们将该组件放置在设计窗口中,但位于表单之外。它出现在组件列表中:

Image

现在我们提供程序的相关代码,以便大家全面了解:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.io.*;

public class dialogues extends JFrame {
   // 框架组件
  JPanel contentPane;
  JButton btnSauvegarder = new JButton();
  JButton btnCharger = new JButton();
  JButton btnEffacer = new JButton();
  JScrollPane jScrollPane1 = new JScrollPane();
  JTextArea txtTexte = new JTextArea();
  JFileChooser jFileChooser1 = new JFileChooser();

  // 文件过滤器
  javax.swing.filechooser.FileFilter filtreTxt = null;

   //构建框架
  public dialogues() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
       // 其他初始化
      moreInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }

   // moreInit
  private void moreInit(){
     // 窗口构建时的初始化
     // 过滤器 *.txt
    filtreTxt = new javax.swing.filechooser.FileFilter(){
      public boolean accept(File f){
        // 是否接受 f?
        return f.getName().toLowerCase().endsWith(".txt");
      }//接受
      public String getDescription(){
        // 过滤器描述
        return "Fichiers Texte (*.txt)";
      }//getDescription
    };
     // 添加过滤器
    jFileChooser1.addChoosableFileFilter(filtreTxt);
    // 还希望过滤所有文件
    jFileChooser1.setAcceptAllFileFilterUsed(true);
    // 将 FileChooser 文件夹的起始目录设置为当前目录
    jFileChooser1.setCurrentDirectory(new File("."));
  }

   //初始化组件
  private void jbInit() throws Exception  {
.......................
  }
   //已替换,这样窗口关闭时即可退出
  protected void processWindowEvent(WindowEvent e) {
......................
    }
  }

  void btnCharger_actionPerformed(ActionEvent e) {
    // 使用 JFileChooser 对象选择文件

     // 设置初始过滤器
    jFileChooser1.setFileFilter(filtreTxt);
     // 显示选择框
    int returnVal = jFileChooser1.showOpenDialog(this);
    // 用户是否已选择?
    if(returnVal == JFileChooser.APPROVE_OPTION) {
      // 将文件放入文本框
       lireFichier(jFileChooser1.getSelectedFile());
    }//if
  }//btnCharger_actionPerformed

  // lireFichier
  private void lireFichier(File fichier){
     // 在文本字段中显示文本文件“fichier”的内容

     // 清空文本字段
    txtTexte.setText("");

     // 一些数据
    BufferedReader IN=null;
    String ligne=null;
    try{
      // 以只读模式打开文件
      IN=new BufferedReader(new FileReader(fichier));
      // 逐行读取文件
      while((ligne=IN.readLine())!=null){
        txtTexte.append(ligne+"\n");
      }//while
       // 关闭文件
      IN.close();
    }catch(Exception ex){
       // 发生错误
      txtTexte.setText(""+ex);
    }//catch
  }

  // 清除
  void btnEffacer_actionPerformed(ActionEvent e) {
    // 清空文本框
    txtTexte.setText("");
  }

   // 保存
  void btnSauvegarder_actionPerformed(ActionEvent e) {
     // 将文本框内容保存到文件中

     // 设置初始过滤器
    jFileChooser1.setFileFilter(filtreTxt);
     // 显示保存选择框
    int returnVal = jFileChooser1.showSaveDialog(this);
    // 用户是否已选择?
    if(returnVal == JFileChooser.APPROVE_OPTION) {
      // 将文本框内容写入文件
       écrireFichier(jFileChooser1.getSelectedFile());
    }//if
  }

   // lireFichier
  private void écrireFichier(File fichier){
     // 将文本框内容写入文件

     // 一些数据
    PrintWriter PRN=null;
    try{
       // 以写入模式打开文件
      PRN=new PrintWriter(new FileWriter(fichier));
      // 写入文本框内容
      PRN.print(txtTexte.getText());
       // 关闭文件
      PRN.close();
    }catch(Exception ex){
       // 发生错误
      txtTexte.setText(""+ex);
    }//捕获
  }

对于方法 btnEffacer_Click、lireFichierécrireFichier 的代码,我们将不予讨论,因为它们并未提供任何新内容。 我们将重点探讨 JFileChooser 类及其用法。该类结构复杂,堪称“大杂烩”。在此我们仅使用以下方法:

addChoosableFilter(FileFilter)
用于设定可供选择的文件类型
setAcceptAllFileFilterUsed(boolean)
指定是否在选择中提供“所有文件”类型
File getSelectedFile()
用户选择的文件 (File)
int showSaveDialog()
显示保存选择框的方法。返回类型为 int。值 jFileChooser.APPROVE_OPTION 表示用户做出了有效选择。否则,表示用户取消了选择,或者发生了错误。
setCurrentDirectory
用于设置用户开始浏览文件系统的初始目录
setFileFilter(FileFilter)
用于设置活动过滤器

方法 showSaveDialog 会显示如下所示的选项框:

Image

1
使用方法 addChoosableFilter 构建的下拉列表。它包含所谓的筛选器,由类 FileFilter 表示。开发者需要定义这些筛选器并将其添加到列表中 1.
2
当前文件夹,由方法 setCurrentDirectory 确定(若已使用该方法);否则,当前目录在 Windows 系统中为“我的文档”文件夹,在 Unix 系统中为 home 目录。
3
用户选择或直接输入的文件名。该名称将通过方法 getSelectedFile() 提供
4
“保存/取消”按钮。若使用 Enregistrer 按钮,则 showSaveDialog 方法将返回结果 jFileChooser.APPROVE_OPTION

下拉列表 1 的文件过滤器是如何构建的?使用以下方法将过滤器添加到列表 1 中:

addChoosableFilter(FileFilter)
该方法确定可供选择的文件类型

来自类 JFileChooser。 接下来我们需要了解类 FileFilter实际上,javax.swing.filechooser.FileFilter 是一个抽象类,即 c.a.d。这是一个无法被实例化、只能被派生的类。其定义如下:

FileFilter()
构造函数
boolean accept(File)
指示文件 f 是否属于该过滤器
String getDescription()
过滤器的描述字符串

举个例子。我们希望下拉列表 1 提供一个过滤器,用于选择描述为“文本文件 (*.txt)”*.txt 文件。

  • 我们需要创建一个从 FileFilter 类派生的子类
  • 使用 boolean accept(File f) 方法,如果文件 f 的名称以 .txt 结尾,则返回 true
  • 使用 String 类型的方法 getDescription() 返回描述“文本文件 (*.txt)”

该过滤器可在我们的应用程序中按以下方式定义:

  javax.swing.filechooser.FileFilter filtreTxt = new javax.swing.filechooser.FileFilter(){
    public boolean accept(File f){
      // 是否接受 f?
      return f.getName().toLowerCase().endsWith(".txt");
    }//接受
    public String getDescription(){
      // 过滤器描述
      return "Fichiers Texte (*.txt)";
    }//getDescription
  };

该过滤器将通过以下语句添加到对象 jFileChooser1 的过滤器列表中:

     // 添加过滤器 *.txt
    jFileChooser1.addChoosableFileFilter(filtretxt);

所有这些以及其他一些初始化操作都在窗口构建时执行的 moreInit 方法中完成(参见上文完整程序)。按钮 Sauvegarder 的代码如下:

  void btnSauvegarder_actionPerformed(ActionEvent e) {
    // 将文本框中的内容保存到文件中

     // 设置初始过滤器
    jFileChooser1.setFileFilter(filtreTxt);
     // 显示保存选项框
    int returnVal = jFileChooser1.showSaveDialog(this);
    // 用户是否已做出选择?
    if(returnVal == JFileChooser.APPROVE_OPTION) {
      // 将文本框内容写入文件
       écrireFichier(jFileChooser1.getSelectedFile());
    }//if
  }

操作顺序如下:

  • 将活动筛选器设置为 *.txt,以便用户优先搜索此类文件。同时还提供了“所有文件”筛选器,该筛选器是在流程 moreInit 中添加的。因此共有两个筛选器。
  • 显示保存选择框。此时控制权交由用户,用户通过该选择框指定文件系统中的文件。
  • 当用户离开选择框时,我们会检测返回值以判断是否需要保存文本框。如果需要,则应将其保存到由方法 getSelectedFile 获取的文件中。

“加载”按钮的相关代码与“保存”按钮的代码非常相似。

  void btnCharger_actionPerformed(ActionEvent e) {
    // 使用对象选择文件JFileChooser

     // 设置初始过滤条件
    jFileChooser1.setFileFilter(filtreTxt);
     // 显示选择框
    int returnVal = jFileChooser1.showOpenDialog(this);
    // 用户是否已选择?
    if(returnVal == JFileChooser.APPROVE_OPTION) {
      // 将文件放入文本框
       lireFichier(jFileChooser1.getSelectedFile());
    }//if
  }//btnCharger_actionPerformed

有两个区别:

  • 要显示文件选择框,应使用方法 showOpenDialog 代替方法 showSaveDialog。显示的文件选择框与方法 showSaveDialog 显示的类似。
  • 如果用户已成功选定文件,则调用方法 lireFichier 而不是方法 écrireFichier

5.4.1.1. 选择框 JColorChooser 和 JFontChooser

我们延续前面的示例,添加两个新按钮:

Image

编号
类型
名称
作用
6
JButton
btnCouleur
用于设置 TextBox 的字符颜色
7
JButton
btnPolice
用于设置 TextBox 的字体

用于显示颜色选择框的组件 JColorChooser 可在 JBuilder 的 Swing 组件列表中找到:

Image

我们将该组件放置在设计窗口中,但位于表单之外。它在表单中不可见,但在窗口的组件列表中仍然存在:

Image

JColorChooser 非常简单。我们使用方法 int showDialog 显示颜色选择框:

static Color
showDialog(Component component, String title, Color initialColor)
Component component
颜色选择框的父组件,通常是一个窗口 JFrame
String title
选择框标题栏中的标题
颜色 intialColor
在选择框中最初选定的颜色

如果用户选择了颜色,该方法将返回该颜色;否则将返回值 null。与按钮 Couleur 关联的代码如下:

  void btnCouleur_actionPerformed(ActionEvent e) {
     // 使用组件选择文本颜色 JColorChooser
    Color couleur;
    if((couleur=jColorChooser1.showDialog(this,"Choix d'une couleur",Color.BLACK))!=null){
       // 设置文本框中字符的颜色
      txtTexte.setForeground(couleur);
    }//if
  }

Colorjava.awt.Color 中定义。其中定义了各种常量,包括表示黑色颜色的 Color.BLACK。显示的选择框如下

Image

颇为奇怪的是,Swing 库并未提供用于选择字体的类。所幸互联网上有丰富的 Java 资源。通过搜索关键词“JFontChooser”(这似乎是此类类的可能名称),可以找到多个相关资源。 接下来的示例将演示如何配置 JBuilder,使其使用初始配置中未预设的包。

获取到的包名为 swingextras.jar,并被放置在 <jdk>\jre\lib\perso 文件夹中,其中 <jdk> 指代 JBuilder 所使用的 JDK 安装目录。 它本可以被放置在任何其他位置。

让我们查看 SwingExtras.jar 包的内容:

Image

其中包含该包中提供的组件的 Java 源代码。此外还包含这些类本身:

Image

从该归档文件中可以看出,类 JFontChooser 的实际名称为 com.lamatek.swingextras.JFontChooser。如果不想写全名,我们需要在程序开头写:

import com.lamatek.swingextras.*;

编译器和 Java 虚拟机将如何找到这个新包?关于直接使用 JDK 的情况,我们已在前文说明过,读者可在关于 Java 包的段落中查阅相关解释。本文将详细说明如何使用 JBuilder。 包的配置位于“选项/配置 JDK”菜单中:

Image

1
通过按钮 Modifier (1),可告知 JBuilder 使用哪个 JDK。 在此示例中,JBuilder 随附了 JDK 1.3.1 版本。 当 JDK 1.4 发布时,它与 JBuilder 分开安装,我们通过 Modifier 按钮指示 JBuilder从此使用 JDK 1.4,并指定了后者的安装目录
2
JDK 的基础目录,该目录目前正被 JBuilder 使用
3
JBuilder所使用的Java存档文件(.jar)列表。可通过Ajouter按钮(4)添加其他文件
4
Ajouter(4)按钮用于添加新包,JBuilder将同时使用这些包进行程序编译和运行。此处已使用该按钮添加了SwingExtras.jar(5)包

现在,JBuilder 已正确配置,可以使用 JFontChooser 类。然而,我们需要访问该类的定义才能正确使用它。swingextras.jar 归档文件中包含一些 HTML 文件,我们可以提取这些文件并加以利用。 这其实没有必要。JBuilder 中的 .jar 文件已直接包含 Java 文档。为此,需配置上方的 Documentation (6) 选项卡。随后将出现如下新窗口:

Image

通过 Ajouter 按钮,我们可以指定需对 SwingExtras.jar 文件进行文档检索。完成此设置后,即可确认确实可以访问 SwingExtras.jar 的文档。这带来了诸多便利:

  • 如果开始编写导入语句
import com.lamatek.swingextras.*;

,会发现 JBuilder 提供了帮助提示:

Image

已成功找到 com.lamatek 包。

  • 现在,在以下程序中:
import com.lamatek.swingextras.*;

public class test{
  JFontChooser jFontChooser1=null;
}  

在关键词 JFontChooser 上执行 F1,可获得关于该类的帮助:

Image

从状态栏1中可以看到,确实使用了swingextras.jar包中的HTML文件。上面的示例已经足够清晰,我们可以据此编写应用程序中Police按钮的代码:

  void btnPolice_actionPerformed(ActionEvent e) {
    // 使用组件选择文本字体 JFontChooser
    jFontChooser1 = new JFontChooser(new Font("Arial", Font.BOLD, 12));
    if (jFontChooser1.showDialog(this, "Choix d'une police") == JFontChooser.ACCEPT_OPTION) {
      // 更改文本框中字符的字体
      txtTexte.setFont(jFontChooser1.getSelectedFont());
    }//if
  }

上述方法 showDialog 显示的选项框如下:

Image

5.5. 图形应用程序 IMPOTS

我们再次使用已处理过两次的应用程序 IMPOTS。现在,我们为其添加一个图形界面:

Image

控件如下

编号
类型
名称
角色
1
JRadioButton
rdOui
已婚者请勾选
2
JRadioButton
rdNon
未婚者请勾选
3
JSpinner
spinEnfants
纳税人的子女数量
最小值=0,最大值=20,增量=1
4
JTextField
txtSalaire
纳税人年薪(单位:F)
5
JLabel
lblImpots
应缴税额
6
JTextField
txtStatus
状态消息字段 - 不可修改

菜单如下:

主选项
次要选项
名称
角色
税收
   
 
初始化
mnuInitialiser
从文本文件中加载计算所需的数据
 
计算
mnuCalculer
在所有必要数据齐全且正确时计算应缴税款
 
清除
mnuEffacer
将表格恢复为初始状态
 
退出
mnuQuitter
退出应用程序

操作规则

  • 只要工资字段为空,Calculer菜单将保持关闭
  • 如果启动计算后发现工资有误,将提示错误:

Image

程序代码如下。它使用了在“类”一章中创建的类 impots。JBuilder 自动生成的部分代码在此未予复制。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.io.*;
import java.util.*;
import javax.swing.event.*;

public class frmImpots extends JFrame {
  // 窗口组件
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuInitialiser = new JMenuItem();
  JMenuItem mnuCalculer = new JMenuItem();
  JMenuItem mnuEffacer = new JMenuItem();
  JMenuItem mnuQuitter = new JMenuItem();
  JLabel jLabel1 = new JLabel();
  JFileChooser jFileChooser1 = new JFileChooser();
  JTextField txtStatus = new JTextField();
  JRadioButton rdOui = new JRadioButton();
  JRadioButton rdNon = new JRadioButton();
  JLabel jLabel2 = new JLabel();
  JLabel jLabel3 = new JLabel();
  JTextField txtSalaire = new JTextField();
  JLabel jLabel4 = new JLabel();
  JLabel jLabel5 = new JLabel();
  JLabel lblImpots = new JLabel();
  JLabel jLabel7 = new JLabel();
  ButtonGroup buttonGroup1 = new ButtonGroup();
  JSpinner spinEnfants=null;
  FileFilter filtreTxt=null;

   // 类的属性
  double[] limites=null;
  double[] coeffr=null;
  double[] coeffn=null;
  impots objImpots=null;

   //构建框架
  public frmImpots() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // 其他初始化
    moreInit();
  }

   // 表单初始化
  private void moreInit(){
     // “计算”菜单被禁用
    mnuCalculer.setEnabled(false);
    // 输入字段被禁用
    txtSalaire.setEditable(false);
    txtSalaire.setBackground(Color.WHITE);
    // 状态字段
    txtStatus.setBackground(Color.WHITE);
    // “子女”下拉框 - 0 至 20 名子女
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(137,60,40,27));
    contentPane.add(spinEnfants);
     // 下拉框的 *.txt 过滤器
    filtreTxt = new javax.swing.filechooser.FileFilter(){
      public boolean accept(File f){
        // 是否接受 f?
        return f.getName().toLowerCase().endsWith(".txt");
      }//接受
      public String getDescription(){
        // 过滤器描述
        return "Fichiers Texte (*.txt)";
      }//getDescription
    };
     // 添加过滤器 *.txt
    jFileChooser1.addChoosableFileFilter(filtreTxt);
    // 还希望过滤所有文件
    jFileChooser1.setAcceptAllFileFilterUsed(true);
    // 设置邮箱的源目录FileChooser
    jFileChooser1.setCurrentDirectory(new File("."));
  }//moreInit

   //初始化组件
  private void jbInit() throws Exception  {

................

  }

   //已替换,因此窗口关闭时我们可以退出
  protected void processWindowEvent(WindowEvent e) {
.....................
  }

  void mnuQuitter_actionPerformed(ActionEvent e) {
    // 退出应用程序
    System.exit(0);
  }

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // 加载数据文件
     // 通过选择框 JFileChooser1 进行选择
    jFileChooser1.setFileFilter(filtreTxt);
    if (jFileChooser1.showOpenDialog(this)!=JFileChooser.APPROVE_OPTION)
      return;
     // 处理所选文件
    try{
       // 读取数据
      lireFichier(jFileChooser1.getSelectedFile());
       // 创建税款对象
      objImpots=new impots(limites,coeffr,coeffn);
       // 确认
      txtStatus.setText("Données chargées");
       // 工资可修改
      txtSalaire.setEditable(true);
       // 无法再进行更改
      mnuInitialiser.setEnabled(false);
    }catch(Exception ex){
       // 问题
      txtStatus.setText("Erreur : " + ex.getMessage());
      // 结束
      return;
    }//捕获
  }

  private void lireFichier(File fichier) throws Exception {
     // 数据表
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();
    String[] champs=null;

     // 以读取模式打开文件
    BufferedReader IN=new BufferedReader(new FileReader(fichier));
    // 逐行读取文件
     // 它们的形式为极限 coeffr coeffn
    String ligne=null;
    int numLigne=0;           // 当前行号
    while((ligne=IN.readLine())!=null){
      // 再下一行
      numLigne++;
       // 将该行拆分为字段
      champs=ligne.split("\\s+");
       // 3 个字段?
      if(champs.length!=3)
        throw new Exception("ligne " + numLigne + "erronée dans fichier des données");
       // 获取这三个字段
       aLimites.add(champs[0]);
       aCoeffR.add(champs[1]);
       aCoeffN.add(champs[2]);
    }//while
     // 关闭文件
    IN.close();
     // 将数据传输到限定范围的数组中
    int n=aLimites.size();
    limites=new double[n];
    coeffr=new double[n];
    coeffn=new double[n];
    for(int i=0;i<n;i++){
      limites[i]=Double.parseDouble((String)aLimites.get(i));
      coeffr[i]=Double.parseDouble((String)aCoeffR.get(i));
      coeffn[i]=Double.parseDouble((String)aCoeffN.get(i));
    }//for
  }

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // 计算税款
     // 工资核对
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // 错误消息
      txtStatus.setText("Salaire incorrect. Recommencez");
       // 聚焦错误字段
      txtSalaire.requestFocus();
       // 返回界面
      return;
    }
     // 子女人数
    Integer InbEnfants=(Integer)spinEnfants.getValue();
     // 税款计算
    lblImpots.setText(""+objImpots.calculer(rdOui.isSelected(),InbEnfants.intValue(),salaire));
     // 清除状态消息
    txtStatus.setText("");
  }

  void txtSalaire_caretUpdate(CaretEvent e) {
     // 工资发生变化 - 更新“计算”菜单
    mnuCalculer.setEnabled(! txtSalaire.getText().trim().equals(""));
  }

  void mnuEffacer_actionPerformed(ActionEvent e) {
    // 清空表单
    rdNon.setSelected(true);
    spinEnfants.getModel().setValue(new Integer(0));
    txtSalaire.setText("");
    mnuCalculer.setEnabled(false);
    lblImpots.setText("");
  }
}

这里我们使用了一个仅在 JDK 1.4 及以上版本中提供的组件,即 JSpinner。 这是一个递增控件,在此允许用户设定子节点的数量。在用于测试的 JBuilder 6 版本中,该 Swing 组件并未出现在 Swing 组件栏中。尽管如此,它仍然可以使用,只是操作起来比组件栏中的组件稍显复杂。 实际上,在设计阶段无法将JSpinner组件拖放到表单上,必须在运行时进行操作。让我们看看实现该功能的代码:

     // 子女数量选择器 - 输入0至20名子女
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(137,60,40,27));
    contentPane.add(spinEnfants);

第一行创建了组件 JSpinner。该组件可用于多种用途,而不仅仅是像这里所示的整数递增器。 构造函数 JSpinner 的参数是一个数字递增器模板,接受四个参数(值、最小值、最大值、增量)。该组件的格式如下:

Image

valeur
组件中显示的初始值
min
组件中可显示的最小值
max
组件中可显示的最大值
incrément
使用组件的上下箭头时,显示值的增量

组件的值是通过其方法 getValue 获取的,该方法返回类型为 Object。因此需要进行一些类型转换才能得到所需的整数:

     // 子女数量
    Integer InbEnfants=(Integer)spinEnfants.getValue();
     // 计算税款
    lblImpots.setText(""+objImpots.calculer(rdOui.isSelected(),
      InbEnfants.intValue(),salaire));

定义完组件 JSpinner 后,它会被放置在窗口中:

    spinEnfants.setBounds(new Rectangle(137,60,40,27));
    contentPane.add(spinEnfants);

首先,用户必须使用菜单选项 Initialiser,该选项将通过构造函数创建一个 impots 对象

    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception

。需要提醒的是,该构造函数已在“类”一章的示例中定义。如有需要,请参阅该章节。构造函数所需的三个数组将根据以下格式的文本文件内容进行填充:

12620.0
0
0
13190
0.05
631
15640
0.1
1290.5
24740
0.15
2072.5
31810
0.2
3309.5
39970
0.25
4900
48360
0.3
6898.5
55790
0.35
9316.5
92970
0.4
12106
127860
0.45
16754.5
151250
0.50
23147.5
172040
0.55
30710
195000
0.60
39312
0
0.65
49062

每行包含三个数字,之间至少有一个空格。该文本文件由用户通过 JFileChooser 组件选定。 一旦创建了类型为 impots 的对象,只需让用户提供所需的三个信息:已婚与否、子女数量、年薪,然后调用类 impots 的方法 calculer。 该操作可以重复多次。而类型为 impots 的对象仅在使用 Initialiser 选项时构建一次。

5.6. 编写小程序

5.6.1. 简介

编写好一个带图形界面的应用程序后,将其转换为applet格式相当简单。该文件存储在机器A上,可以通过互联网上的机器B上的网页浏览器进行下载。 这样,原始应用程序便可在众多用户之间共享,这也是将应用程序转换为小程序的主要优势。然而,并非所有应用程序都能进行此类转换:为了不影响在浏览器中使用小程序的用户,小程序的环境受到严格限制:

  • 小程序无法读取或写入用户的磁盘
  • 它只能与浏览器下载它的那台机器进行通信。

这些限制非常严格。例如,这意味着需要从文件或数据库中读取信息的应用程序,必须通过位于其下载源服务器上的中继应用程序来请求这些信息。

一个简单Web应用程序的一般结构如下:

Image

  • 客户端通常通过浏览器向 Web 服务器请求文档 HTML。该文档可能包含一个小程序,该小程序将在客户端浏览器显示的文档 HTML 中作为独立的图形应用程序运行。
  • 该小程序可以访问数据,但仅限于位于 Web 服务器上的数据。它既无法访问运行它的客户端机器上的资源,也无法访问网络中除下载源之外的其他机器上的资源。

5.6.2. JApplet 类

5.6.2.1. Définition

如果应用程序是类 java.applet.Applet 或类 javax.swing.JApplet 的实例,则可以通过 Web 浏览器下载。 后者派生自前者,而前者又派生自类 Panel,该类本身又派生自类 Container。 由于 AppletJApplet 实例属于 container 类型,因此可以包含诸如按钮、复选框、列表等组件(Component)。 下面简要说明各种方法的作用:

方法
作用
public void destroy();
销毁 Applet 实例
public AppletContext getAppletContext();
获取 Applet 的运行环境(其所在的 HTML 文档、同一文档中的其他 Applet 等)
public String getAppletInfo();
返回一个字符串,提供有关该小程序的信息
public AudioClip getAudioClip(URL url);
加载由 URL 指定的音频文件
public AudioClip getAudioClip(URL url, String name);
加载由 URL/name 指定的音频文件
public URL getCodeBase();
返回小程序的URL
public URL getDocumentBase();
从包含小程序的文档 HTML 中获取 URL
public Image getImage(URL url);
获取由 URL 指定的图像
public Image getImage(URL url, String name);
获取由 URL/name 指定的图片
public String getParameter(String name);
获取包含该小程序的文档 HTML 中 <applet> 标签内 name 参数的值。
public void init();
该方法由浏览器在小程序首次启动时调用
public boolean isActive();
小程序状态
public void play(URL url);
播放由 URL 指定的音频文件
public void play(URL url, String name);
播放由 UR/name 指定的音频文件
public void resize(Dimension d);
设置小程序框架的尺寸
public void resize(int width, int height);
同上
public final void setStub(AppletStub stub);
 
public void showStatus(String msg);
在小程序的状态栏中显示一条消息
public void start();
每次显示包含该小程序的文档时,浏览器都会调用此方法
public void stop();
当用户切换至其他文档(即用户将URL替换为其他文档)时,浏览器会调用此方法

JApplet 类对 Applet 类进行了一些改进,特别是能够包含 JMenuBar 和 c.a.d 组件。 ,而 Applet 类无法实现这一点。

5.6.2.2. 运行 Applet:init、start 和 stop 方法

当浏览器加载一个小程序时,会调用该小程序的三个方法:

init
该方法在小程序初始加载时被调用。因此,应在此处进行应用程序所需的初始化操作。
start
每当包含 Applet 的文档成为浏览器的当前文档时,都会调用此方法。因此,当用户加载 Applet 时,init start 方法将按此顺序执行。当用户离开该文档以查看其他文档时,将执行 stop 方法。当用户稍后返回时,将执行 start 方法。
stop
每当用户离开包含小程序的文档时,都会调用此方法。

对于许多小程序而言,仅需使用 init 方法。只有当应用程序启动了在后台并行且持续运行的任务(线程)——这些任务通常在用户不知情的情况下运行——时,才需要使用 startstop 方法。当用户离开文档时,应用程序的可见部分会消失,但这些后台任务仍会继续运行。这通常是多余的。 因此,我们可以利用浏览器对 stop 方法的调用来停止这些任务。如果用户返回该文档,则利用浏览器对 start 方法的调用重新启动它们。

以一个在图形界面中包含时钟的小程序为例。该时钟由一个后台任务(线程)维护。当用户在浏览器中离开该小程序的页面时,已不可见的时钟便无需继续维护:在小程序的 stop 方法中,我们将停止管理时钟的线程。 在 start 方法中,我们将重新启动该线程,以便当用户返回 Applet 页面时,能看到显示正确时间的时钟。

5.6.3. 将图形应用程序转换为小程序

此处假设该转换是可行的,即其符合 Applet 的运行限制。应用程序通过其某个类的 main 方法启动:


public class application{
    
    public static main void(String arg[]){
        
    }
    
}// 结束

此类应用程序将按以下方式转换为小程序:

import java.applet.JApplet;

public class application extends JApplet{
    
    public void init(){
        
    }
    
}// 班级结束

请注意以下几点:

  1. application 现在继承自类 JApplet
  2. 方法 main 已被方法 init 取代。

作为示例,我们回顾一个已研究的应用程序:列表管理。

Image

该窗口的组件如下:

编号
类型
名称
作用
1
JTextField
txtSaisie
输入字段
2
JList
jList1
容器中的列表jScrollPane1
3
JList
jList2
包含在容器中的列表 jScrollPane2
4
JButton
cmd1To2
将列表 1 中选中的项目转移到列表 2
5
JButton
cmd2To1
执行相反操作
6
JButton
cmdRaz1
清空列表 1
7
JButton
cmdRaz2
清空列表 2

该应用程序的框架如下:

public class interfaceAppli extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JLabel jLabel2 = new JLabel();
  JLabel jLabel3 = new JLabel();
  JTextField txtSaisie = new JTextField();
  JButton cmd1To2 = new JButton();
  JButton cmd2To1 = new JButton();
  DefaultListModel v1=new DefaultListModel();
  DefaultListModel v2=new DefaultListModel();
  JList jList1 = new JList(v1);
  JList jList2 = new JList(v2);
  JScrollPane jScrollPane1 = new JScrollPane();
  JScrollPane jScrollPane2 = new JScrollPane();
  JButton cmdRaz1 = new JButton();
  JButton cmdRaz2 = new JButton();
  JLabel jLabel4 = new JLabel();

   /**构建框架*/
  public interfaceAppli() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }//interfaceAppli

   /**初始化组件*/
  private void jbInit() throws Exception  {
    ...
    txtSaisie.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        txtSaisie_actionPerformed(e);
      }
    });
        ...
         // Jlist1 被放置在容器中 jScrollPane1
    jScrollPane1.getViewport().add(jList1, null);
        // Jlist2 被放置在容器中 jScrollPane2
    jScrollPane2.getViewport().add(jList2, null);
        ...
  }
   /**已替换,因此当窗口关闭时我们可以退出*/
  protected void processWindowEvent(WindowEvent e) {
...
  }

  void txtSaisie_actionPerformed(ActionEvent e) {
.............
}//类
  void cmd1To2_actionPerformed(ActionEvent e) {
..........
  }//转移
  void cmdRaz1_actionPerformed(ActionEvent e) {
    // 清空列表 1
    v1.removeAllElements();
  }//cmd 清空1

  void cmdRaz2_actionPerformed(ActionEvent e) {
    // 清空列表 2
    v2.removeAllElements();
  }///cmd Raz2

要将类 appli 转换为 applet,请按以下步骤操作:

  • 修改之前的类 interfaceAppli,使其不再从 JFrame 派生,而是从 JApplet 派生:
public class interfaceAppli extends JApplet {
  • 删除用于设置窗口标题的指令 JFrame。Applet JApplet 没有标题栏
    // this.setTitle("JList");
  • 将构造函数改为方法 init,并在该方法中删除窗口事件处理(WindowListener 等)。Applet 是一个无法调整大小或关闭的容器。
   /**构建框架*/
  public init() {
    // enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }//interfaceAppli
  • 方法 processWindowEvent 应被删除或注释掉
   /**已替换,这样当窗口关闭时我们可以退出*/
  //protected void processWindowEvent(WindowEvent e) {
  //  super.processWindowEvent(e);
  //  if (e.getID() == WindowEvent.WINDOW_CLOSING) {
  //    System.exit(0);
  //  }
  // }

以下列出该小程序的完整代码(不包括由 JBuilder 自动生成的部分)

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class interfaceAppli extends JApplet {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JLabel jLabel2 = new JLabel();
  JLabel jLabel3 = new JLabel();
  JTextField txtSaisie = new JTextField();
  JButton cmd1To2 = new JButton();
  JButton cmd2To1 = new JButton();
  DefaultListModel v1=new DefaultListModel();
  DefaultListModel v2=new DefaultListModel();
  JList jList1 = new JList(v1);
  JList jList2 = new JList(v2);
  JScrollPane jScrollPane1 = new JScrollPane();
  JScrollPane jScrollPane2 = new JScrollPane();
  JButton cmdRaz1 = new JButton();
  JButton cmdRaz2 = new JButton();
  JLabel jLabel4 = new JLabel();

   /**构建框架*/
  public void init() {
    // enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }//interfaceAppli

   /**初始化组件*/
  private void jbInit() throws Exception  {
    //setIconImage(Toolkit.getDefaultToolkit().createImage(interfaceAppli.class.getResource("[Votre icône]")));
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
    this.setSize(new Dimension(400, 308));
    // this.setTitle("JList");
    jScrollPane1.setBounds(new Rectangle(37, 133, 124, 101));
    jScrollPane2.setBounds(new Rectangle(232, 132, 124, 101));
.............
  }

  void txtSaisie_actionPerformed(ActionEvent e) {
     // 输入的文本已通过验证
     // 获取文本时去除了首尾空格
    String texte=txtSaisie.getText().trim();
     // 如果为空,则不采用
    if(texte.equals("")){
      // 错误信息
      JOptionPane.showMessageDialog(this,"Vous devez taper un texte",
        "Erreur",JOptionPane.WARNING_MESSAGE);
      // 结束
      return;
    }//if
    // 如果非空,将其添加到列表1的值中
    v1.addElement(texte);
     // 并清空输入字段
    txtSaisie.setText("");
  }

  void cmd1To2_actionPerformed(ActionEvent e) {
    // 将列表 1 中选中的项目转移到列表 2
    transfert(jList1,jList2);
  }//cmd1To2

  private void transfert(JList L1, JList L2){
      // 将列表 1 中选中的项目转移到列表 2
       // 从 L1 中获取所选元素的索引数组
      int[] indices=L1.getSelectedIndices();
      // 需要做些什么?
      if (indices.length==0) return;
      // 从 L1 中获取数值
      DefaultListModel v1=(DefaultListModel)L1.getModel();
      // 以及 L2 中的值
      DefaultListModel v2=(DefaultListModel)L2.getModel();
      for(int i=indices.length-1;i>=0;i--){
        // 将 L1 中选中的值添加到 L2
        v2.addElement(v1.elementAt(indices[i]));
        // 从 L1 复制到 L2 的元素必须从 L1 中删除
        v1.removeElementAt(indices[i]);
      }//用于
  }//转移

  private void affiche(String message){
    // 显示消息
      JOptionPane.showMessageDialog(this,message,
        "Suivi",JOptionPane.INFORMATION_MESSAGE);
  }// 显示

  void cmd2To1_actionPerformed(ActionEvent e) {
    // 将 jList2 中选定的元素转移到 jList1
      transfert(jList2,jList1);
  }//cmd2TO1

  void cmdRaz1_actionPerformed(ActionEvent e) {
    // 清空列表 1
    v1.removeAllElements();
  }//cmd Raz1

  void cmdRaz2_actionPerformed(ActionEvent e) {
    // 清空列表 2
    v2.removeAllElements();
  }//cmd Raz2

}//分类

可以编译此小程序的源代码。这里使用的是JDK,不使用JBuilder。

E:\data\serge\Jbuilder\interfaces\JApplets\1>javac.bat interfaceAppli.java

E:\data\serge\Jbuilder\interfaces\JApplets\1>dir
12/06/2002  16:40                6 148 interfaceAppli.java
12/06/2002  16:41                  527 interfaceAppli$1.class
12/06/2002  16:41                  525 interfaceAppli$2.class
12/06/2002  16:41                  525 interfaceAppli$3.class
12/06/2002  16:41                  525 interfaceAppli$4.class
12/06/2002  16:41                  525 interfaceAppli$5.class
12/06/2002  16:41                4 759 interfaceAppli.class

该应用程序可通过 AppletViewer 程序进行测试,该程序属于 JDK 套件,可用于运行小程序或浏览器。 为此,需创建文档 HTML appli.htm,其中包含以下小程序:

<html>
    <head>
        <title>listes swing</title>
    </head>
    <body>
        <applet 
        code="interfaceAppli.class"
      width="400"
      height="300">
        </applet>
    </body>
</html>

这是一个典型的 HTML 文档,只是其中包含了一个 applet 标签该标签使用了三个参数:

code="interfaceAppli.class"
要加载以运行小程序的已编译 JAVA 类名称
width="400"
applet在文档中的框架宽度
height="300"
文档中小程序框架的高度

当文件 appli.htm 生成后,可通过 JDK 中的程序 appletviewer 或使用浏览器加载该文件。在 appli.htm 文件所在的文件夹中,于 DOS 窗口中输入以下命令:

appletviewer appli.htm

此处假设可执行文件 appletviewer.exe 位于 DOS 窗口中的 PATH 目录下,否则需提供该可执行文件的完整路径。随后将显示以下窗口:

Image

使用选项 Applet/Quitter 停止小程序的执行。 现在我们用浏览器测试该小程序。浏览器必须使用 Java 2 虚拟机才能显示 Swing 组件。直到最近(2001年),这一限制曾造成问题,因为 Sun 发布的 JDK 版本,浏览器厂商往往滞后很久才跟进。 最终,Sun 通过向用户提供名为“Java 插件”的应用程序,消除了这一障碍。该插件使 Internet Explorer 和 Netscape Navigator 浏览器能够使用 Sun 发布的最新 JRE(JRE=Java 运行时环境)。 以下测试是在配备了Java 1.4插件的IE 5.5环境下进行的。

测试 interfaceAppli.class 小程序的第一种方法是双击 HTML appli.htm 文件,将其直接加载到浏览器中。 此时,浏览器将直接显示页面 HTML 及其小程序,无需 Web 服务器:

Image

从 URL (1) 可以看出,该文件已直接加载到浏览器中。在下面的示例中,文件 appli.htm 是从运行在本地机器 81 端口的 Apache Web 服务器上请求的:

Image

5.6.4. HTML文档中的格式化选项 <applet>

我们看到,在文档 HTML 中,该小程序通过格式化命令(标签)<applet> 进行引用。该命令可以包含多种参数:

<applet
    code=
    width=
    height=
    codebase=
    align=
    hspace=
    vspace=
alt=
>
    <param name1=nom1 value=val1>
    <param name2=nom2 value=val2>
    texte
>
</applet>

参数的含义如下:

参数
含义
code
必填 - 要执行的 .class 文件名
width
必填 - 小程序在文档中的宽度
height
必填 - 高度 …
codebase
可选 - 包含待执行小程序的目录路径。若未指定 codebase,则在引用该小程序的 HTML 文档所在目录中查找
align
可选 - 小程序在文档中的位置(LEFT、RiGHT、CENTER)
hspace
可选 - 小程序周围的水平边距(以像素为单位)
vspace
可选 - 围绕小程序的垂直边距,以像素为单位
alt
可选 - 如果浏览器无法加载小程序,则显示的替代文本
param
可选 - 传递给小程序的参数,指定其名称(name)和值(value)。小程序可通过 val1=getParameter("nom1") 获取参数 nom1 的值
可以使用任意数量的参数
texte
可选 - 将由无法执行小程序的浏览器显示,例如因为该浏览器不具备 Java 虚拟机。

下面通过一个示例说明如何向小程序传递参数:

<html>
    <head>
        <title>paramètres applet</title>
    </head>
    <body>
        <applet code="interfaceParams.class" width="400" height="300">
      <param name="param1" value="val1">
      <param name="param2" value="val2">      
        </applet>
    </body>
</html>

interfaceParams 小程序的 Java 代码已按前述方式生成。我们构建了一个名为 JBuilder 的应用程序,然后对其进行了上述提到的若干转换:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class interfaceParams extends JApplet {
  JPanel contentPane;
  JScrollPane jScrollPane1 = new JScrollPane();
  JLabel jLabel1 = new JLabel();
  DefaultListModel params=new DefaultListModel();
  JList lstParams = new JList(params);

  //构建框架
  public void init() {
    // enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // 其他初始化
    moreInit();
  }

   // 初始化
  public void moreInit(){
     // 显示小程序的参数值
    params.addElement("nom=param1 valeur="+getParameter("param1"));
    params.addElement("nom=param2 valeur="+getParameter("param2"));
  }//更多初始化

   //初始化组件
  private void jbInit() throws Exception  {
............
    this.setSize(new Dimension(205, 156));
    //this.setTitle("Applet 参数");
    jScrollPane1.setBounds(new Rectangle(19, 53, 160, 73));
............
 }
}

使用 AppletViewer 执行:

Image

5.6.5. 从小程序访问远程资源

许多应用程序需要利用文件或数据库中的信息。我们曾指出,Applet无法访问其运行所在机器上的资源。这是一项合乎常理的措施。 否则,只需编写一个小程序就能“窥探”加载者的硬盘。不过,小程序仍可访问其下载来源服务器的资源,例如文件。接下来我们将探讨这一点。

5.6.5.1. URL类

任何 Java 应用程序都可以通过 java.net.URL 类(URL=统一资源定位符)读取网络中某台机器上的文件。 一个 URL 标识一个网络资源、其所在的机器,以及用于获取该资源的通信协议和端口,格式如下:

协议:端口//主机/文件

因此,URL http://www.ibm.com/index.html 指代位于 www.ibm.com 机器上的 index.html 文件。 可通过协议 http 访问该文件。端口未指定:浏览器将使用 80 端口,即 http 服务的默认端口。

下面详细说明 URL 类中的部分元素:

public URL(String spec)
根据“协议:端口//主机/文件”格式的字符串创建 URL 实例——若字符串语法不符合 URL 规范,则抛出异常
  
public String getFile()
用于获取 URL 中 "协议:端口//主机/文件" 字符串的文件字段
  
public String getHost()
用于从 URL 中的字符串 "协议:端口//主机/文件" 中获取主机字段
  
public String getPort()
用于获取 URL 中字符串 "协议:端口//主机/文件" 的端口字段
  
public String getProtocol()
用于获取 URL 中字符串 "协议:端口//主机/文件" 的协议字段
  
public URLConnection openConnection()
根据协议 getProtocol() 通过端口 getPort() 与远程机器 getHost() 建立连接,以便读取文件 getFile()。 如果无法建立连接,则抛出异常
  
public final InputStream openStream()
openConnection() 的快捷方式。getInputStream()。用于获取一个输入流,以便从中读取 getFile() 文件的内容。如果无法获取该流,则抛出异常
  
public String toString()
显示 URL 实例的标识

这里有两个我们感兴趣的方法:

  • public URL(String spec) 用于指定要处理的文件
  • public final InputStream openStream() 用于处理该文件

5.6.5.2. 一个控制台示例

我们将编写一个没有图形界面的 Java 程序,用于在屏幕上显示作为参数传递给它的 URL 的内容。调用示例如下:

    DOS> java urlcontenu http://istia.univ-angers.fr/index.html

该程序相对简单:


import java.net.*;             // 用于类 URL
import java.io.*;                // 用于流(stream)

public class urlcontenu{

// 显示作为参数传递的 URL 的内容
// 该内容必须为文本才能被读取

  public static void main (String arg[]){
    // 参数验证
    if(arg.length==0){
      System.err.println("Syntaxe pg url");
      System.exit(0);
    }
    
    try{
      // 创建 URL      
      URL url=new URL(arg[0]);
      // 读取内容
      try{
        // 创建输入流
        BufferedReader is=new BufferedReader(new InputStreamReader(url.openStream()));
        try{
          // 读取输入流中的文本行
          String ligne;
          while((ligne=is.readLine())!=null)
            System.out.println(ligne);
        } catch (Exception e){
            System.err.println("Erreur lecture : " +e);
        }
        finally{
          try { is.close(); } catch (Exception e) {}
        }
      } catch (Exception e){
          System.err.println("Erreur openStream : " +e);
      }
    } catch (Exception e){
      System.err.println("Erreur création URL : " +e);
    }
  }// main结束
}// 类结束

在确认确实存在参数后,尝试使用该参数创建一个 URL:


// 创建 URL      
      URL url=new URL(arg[0]);

如果参数不符合 URL protocole:port//machine/fichier 的语法,则创建操作可能会失败。如果有一个语法正确的 URL,则尝试创建一个输入流,以便从中读取文本行。 由 URL URL.openStream() 提供的输入流属于 InputStream 类型,即字符导向流:读取过程按字符逐个进行,需自行组装成行。 为了能够读取文本行,必须使用 readLine 方法,该方法属于 BufferedReaderDataInputStream.On 类,此处选择了 BufferedReader。 接下来只需将 InputStream 流转换为 URL 流,再将其转换为 BufferedReader 流。具体操作是创建一个中间流 InputStreamReader


BufferedReader is=new BufferedReader(new InputStreamReader(url.openStream()));

此流程创建可能失败。需处理相应的异常。一旦获得流程,我们只需从中读取文本行并将其显示在屏幕上。


            String ligne;
         while((ligne=is.readLine())!=null)
                System.out.println(ligne);

同样,读取操作也可能引发异常,需要进行处理。以下是一个调用本地 URL 的程序运行示例:

E:\data\serge\Jbuilder\interfaces\JApplets\3>java urlcontenu http://localhost
<html>

<head>


<title>Bienvenue dans le Serveur Web personnel</title>
</head>

<body bgcolor="#FFFFFF" link="#0080FF" vlink="#0080FF"
topmargin="0" leftmargin="0">

<table border="0" cellpadding="0" cellspacing="0" width="100%"
bgcolor="#000000">
............
                </table>
                </center></div></td>
            </tr>
        </table>
        </td>
    </tr>
</table>
</body>
</html>

5.6.5.3. 图形示例

图形界面如下所示:

Image

编号
名称
类型
角色
1
txtURL
JTextField
URL 请阅读
2
btnCharger
JButton
启动URL播放的按钮
3
JScrollPane1
JScrollPane
滚动面板
4
lstURL
JList
显示所请求的URL内容的列表

运行时,我们得到的结果与控制台程序类似:

Image

应用程序的相关代码如下:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.net.*;
import java.io.*;

public class interfaceURL extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JTextField txtURL = new JTextField();
  JButton btnCharger = new JButton();
  JScrollPane jScrollPane1 = new JScrollPane();
  DefaultListModel lignes=new DefaultListModel();
  JList lstURL = new JList(lignes);

  //构建框架
  public interfaceURL() {
..............
  }

   //初始化组件
  private void jbInit() throws Exception  {
..............
  }

   //已替换,因此窗口关闭时我们可以退出
  protected void processWindowEvent(WindowEvent e) {
...................
  }

  void txtURL_caretUpdate(CaretEvent e) {
    // 设置“加载”按钮的状态
    btnCharger.setEnabled(! txtURL.getText().trim().equals(""));
  }

  void btnCharger_actionPerformed(ActionEvent e) {
    // 在列表中显示 URL 的内容
    try{
      afficherURL(txtURL.getText().trim());
    }catch(Exception ex){
       // 显示错误
      JOptionPane.showMessageDialog(this,"Erreur : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
    }//try-catch
  }

  private void afficherURL(String strURL) throws Exception {
    // 在列表中显示 URL strURL 的内容
     // 未专门处理任何异常。仅将其上报

     // 创建 URL
    URL url=new URL(strURL);
     // 创建输入流
    BufferedReader IN=new BufferedReader(new InputStreamReader(url.openStream()));
    // 读取输入流中的文本行
    String ligne;
    while((ligne=IN.readLine())!=null)
      lignes.addElement(ligne);
     // 关闭读取流
    IN.close();
  }
}

5.6.5.4. 一个小程序

如前所述,将前面的图形应用程序转换为小程序。该小程序被嵌入到文档 HTML appliURL.htm 中:

<html>
  <head>
    <title>Applet lisant une URL</title>
  </head>
  <body>
    <h1>Applet lisant une URL</h1>
    <applet
      code="appletURL.class"
      width="400"
      height="300"
    >
    </applet>
  </body>
</html>

Image

在上例中,浏览器向运行在 81 端口的 Web 服务器请求了 URL http://localhost:81/Japplets/2/appliURL.htm。 随后,该小程序在浏览器中显示出来。 在该小程序中,我们再次请求了 URL http://localhost:81/Japplets/2/appliURL.htm,以验证是否确实获得了我们构建的 appliURL.htm 文件。 现在,让我们尝试加载一个不属于生成该小程序的机器的 URL 文件(此处为 localhost):

Image

加载 URL 被拒绝。这里再次体现了小程序的限制:它们只能访问其被下载的机器上的网络资源。 Applet 有一种绕过此限制的方法,即把网络请求委托给位于其下载源机器上的服务器程序。该程序将代替 Applet 进行网络请求,并向其返回结果。这被称为中继程序。

5.7. 小程序 IMPOTS

现在我们将图形应用程序 IMPOTS 转换为 Applet。这具有一定的意义:该应用程序将可供任何拥有浏览器和 Java 1.4 插件(由于 JSpinner 组件)的互联网用户使用。 我们的应用程序由此走向全球……这一修改不仅需要进行如今已成常规的图形应用程序转Applet操作,还需要更多步骤。该应用程序使用了一个名为Initialiser的菜单选项,其目的是读取本地文件的内容。然而,如果我们设想一个Web应用程序,就会发现这种模式不再适用:

Image

显然,定义税率表的数据将存储在Web服务器上,而非每台客户端机器上。此处需读取的Web服务器文件将放置在Applet所在的同一文件夹中,而Initialiser选项必须从该位置获取该文件。 前面的示例已经展示了如何实现这一点。此外,之前为了在本地选择数据文件,我们曾使用过一个名为 JFileChooser 的组件,但在此处已无必要。

包含小程序的 HTML 文档如下:

<html>
  <head>
    <title>Applet de calcul d'impôts</title>
  </head>
  <body>
    <h2>Calcul d'impôts</h2>
    <applet
      code="appletImpots.class"
      width="320"
      height="270"
    >

<param name="data" value="impots.txt">


    </applet>
  </body>
</html>

请注意参数 data,其值为包含税率表数据的文件名。 文档 HTML、类 appletImpots、类 impots 以及文件 impots.txt 均位于 Web 服务器的同一文件夹中:

E:\data\serge\web\JApplets\impots>dir
12/06/2002  13:24                  247 impots.txt
13/06/2002  10:17                  654 appletImpots$2.class
13/06/2002  10:17                  653 appletImpots$3.class
13/06/2002  10:17                  653 appletImpots$4.class
13/06/2002  10:17                  651 appletImpots$5.class
13/06/2002  10:06                  651 appletImpots$6.class
13/06/2002  10:17                8 655 appletImpots.class
13/06/2002  10:17                  657 appletImpots$1.class
13/06/2002  10:24                  286 appletImpots.htm
13/06/2002  10:17                1 305 impots.class

该小程序的代码如下(我们仅标出了与图形应用程序相比的改动):

...........
import java.net.*;
import java.applet.Applet;

public class appletImpots extends JApplet {
  // 窗口组件
  JPanel contentPane;
............................

   // 类的属性
  double[] limites=null;
  double[] coeffr=null;
  double[] coeffn=null;
  impots objImpots=null;
  String urlDATA=null;

  //构建框架
  public void init() {
    //enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // 其他初始化
    moreInit();
  }

   // 表单初始化
  private void moreInit(){
     // “计算”菜单被禁用
    mnuCalculer.setEnabled(false);
................
     // 获取税率表文件名
    String nomFichier=getParameter("data");
    // 错误?
    if(nomFichier==null){
       // 错误信息
      txtStatus.setText("Le paramètre data de l'applet n'a pas été initialisé");
       // 禁用“初始化”选项
      mnuInitialiser.setEnabled(false);
      // 结束
      return;
    }//if
     // 设置数据URL
    urlDATA=getCodeBase()+"/"+nomFichier;
  }//moreInit

   //初始化组件
  private void jbInit() throws Exception  {
...................
  }

  void mnuQuitter_actionPerformed(ActionEvent e) {
    // 退出应用程序
    System.exit(0);
  }

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // 加载数据文件
    try{
       // 读取数据
      lireDATA();
       // 创建税费对象
      objImpots=new impots(limites,coeffr,coeffn);
................
  }

  private void lireDATA() throws Exception {
     // 数据表
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();
    String[] champs=null;

     // 以只读模式打开文件
    BufferedReader IN=new BufferedReader(new InputStreamReader(new URL(urlDATA).openStream()));
    // 逐行读取文件
....................
  }

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // 计算税款
......................
  }

  void txtSalaire_caretUpdate(CaretEvent e) {
..........
  }

  void mnuEffacer_actionPerformed(ActionEvent e) {
...............
  }
}

这里我们仅针对因待读取的数据文件现在位于远程机器而非本地机器而带来的变更进行说明:

待读取数据文件的名称 URL 通过以下代码获取:

     // 获取税率表文件名
    String nomFichier=getParameter("data");
    // 错误?
    if(nomFichier==null){
       // 错误信息
      txtStatus.setText("Le paramètre data de l'applet n'a pas été initialisé");
       // 禁用“初始化”选项
      mnuInitialiser.setEnabled(false);
      // 结束
      return;
    }//if
     // 设置数据URL
    urlDATA=getCodeBase()+"/"+nomFichier;

请注意,文件名“impots.txt”已被传递到小程序的 data 参数中:

    <param name="data" value="impots.txt">

因此,上述代码首先会获取参数 data 的值,并处理可能出现的错误。 如果包含该小程序的文档HTML中的URL为http://localhost:81/JApplets/impots/appletImpots.htm, 则文件 impots.txt 对应的 URL 将是 http://localhost:81/JApplets/impots/impots.txt。 我们需要构建该 URL 的名称。小程序的 getCodeBase() 方法会返回包含该小程序的文档 HTML 所在文件夹的 URL, 因此在本例中为http://localhost:81/JApplets/impots。因此,以下语句可用于构建数据文件的URL:

    urlDATA=getCodeBase()+"/"+nomFichier;

在方法 lireFichier() 中,该方法读取 URL 和 urlData 的内容,并创建用于读取数据的输入流:

     // 以读取模式打开文件
    BufferedReader IN=new BufferedReader(new InputStreamReader(new URL(urlDATA).openStream()));

从这一刻起,我们无法再区分数据是来自远程文件还是本地文件。以下是该小程序的运行示例:

Image

5.8. 结论

本章介绍了

  • 使用JBuilder构建图形界面的入门指南
  • 最常用的 Swing 组件
  • Applet的构建

需要记住的是

  • 由 JBuilder 生成的代码可以手动编写。一旦通过某种方式获得了该代码,只需一个简单的 JDK 即可执行它,而 JBuilder 便不再必不可少。
  • 使用 JBuilder 这样的工具可以显著提高工作效率:
    • 虽然可以手动编写 JBuilder 生成的代码,但这可能耗时甚久且意义不大,因为应用程序的核心逻辑通常在于其他方面。
    • 生成的代码具有参考价值。阅读并研究它,是了解首次使用组件时其特定方法和属性的有效途径。
    • JBuilder 支持多平台。因此,在不同平台之间切换时,您已掌握的知识依然适用。能够编写同时在 Windows 和 Linux 系统上运行的程序,无疑是提高生产力的重要因素。但这归功于 Java 本身,而非 JBuilder。

5.9. Linux 上的 JBuilder

上述所有示例均已在 Windows 98 环境下测试。人们可能会质疑,这些程序是否能直接移植到 Linux 系统上运行。只要目标 Linux 机器具备各程序所使用的类,它们即可直接运行。例如,如果您已在 Linux 上安装了 JBuilder,则所需类已存在于您的机器上。 以下是在 Linux 系统上,使用 JBuilder 4 组件运行我们其中一个程序(JList)时的示例:

下面我们将介绍在 Linux 机器上安装 JBuilder 4 Foundation 的过程。在 Win9x 机器上的安装同样简单,且与下文所述流程类似。虽然后续版本的安装可能有所不同,但本文档仍可提供一些有用的信息。

JBuilder可在Inprise网站上获取,网址为:http://www.inprise.com/jbuilder

Image

该页面提供了针对 Windows 和 Linux 的下载链接。下面我们将介绍如何在配备 KDE 图形界面的 Linux 机器上安装 JBuilder。

点击“Jbuilder 4 for Linux”链接后,将显示一个表单。填写表单后,最终将获得两个文件:

jb4docs_fr.tar.gz:Jbuilder4 的文档和示例

jb4fndlinux_fr.tar.gz:JBuilder 4 Foundation。这是 JBuilder 4 商业版的精简版,但在教育环境中已足够使用。

软件的激活密钥将通过电子邮件发送给您。该密钥是使用 JBuilder 4 的必要条件,首次使用时系统会要求您输入。若不慎遗失该密钥,可返回上述网址并点击“获取激活密钥”链接重新获取。 接下来,我们假设您已获取 root,并位于上述两个文件 tar.gz 的目录下。请解压这两个文件:

[tar xvzf jb4fndlinux_fr.tar.gz]
[tar xvzf jb4docs_fr.tar.gz]
[ls -l]
drwxr-xr-x    3 nobody   nobody       4096 oct 10  2000 docs
drwxr-xr-x    3 nobody   nobody       4096 déc  5 13:00 foundation
[ls -l foundation]
-rw-r--r--    1 nobody   nobody       1128 déc  5 13:00 deploy.txt
-rwxr-xr-x    1 nobody   nobody   69035365 déc  5 13:00 fnd_linux_install.bin
drwxr-xr-x    2 nobody   nobody       4096 déc  5 13:00 images
-rw-r--r--    1 nobody   nobody      15114 déc  5 13:00 index.html
-rw-r--r--    1 nobody   nobody      23779 déc  5 13:00 license.txt
-rw-r--r--    1 nobody   nobody      75739 déc  5 13:00 release_notes.html
-rw-r--r--    1 nobody   nobody      31902 déc  5 13:00 whatsnew.html
[ls -l docs]
-rw-r--r--    1 nobody   nobody       1128 oct 10  2000 deploy.txt
-rwxr-xr-x    1 nobody   nobody   40497874 oct 10  2000 doc_install.bin
drwxr-xr-x    2 nobody   nobody       4096 oct 10  2000 images
-rw-r--r--    1 nobody   nobody       9210 oct 10  2000 index.html
-rw-r--r--    1 nobody   nobody      23779 oct 10  2000 license.txt
-rw-r--r--    1 nobody   nobody      75739 oct 10  2000 release_notes.html
-rw-r--r--    1 nobody   nobody      31902 oct 10  2000 whatsnew.html

在生成的两个目录中,文件 .bin 是安装文件。此外,请使用浏览器打开每个目录中的文件 index.html。这些文件提供了安装这两个产品的操作指南。首先安装 JBuilder Foundation:

[cd foundation]
[./fnd_linux_install.bin]

安装程序的第一个界面出现。接下来将出现许多其他界面:

Image

点击确认。随后将显示说明界面:

Image

执行 [suivant]。

Image

接受许可协议,然后执行 [suivant]。

Image

接受 Jbuilder 的建议安装位置(记下来,稍后会用到),然后执行 [suivant]。安装很快就会完成。

Image

现在我们可以进行首次测试了。在 KDE 中,启动文件管理器,进入 Jbuilder 的可执行文件目录:/opt/jbuilder4/bin(如果您将 Jbuilder 安装在 /opt/jbuilder4 中):

Image

点击上方的 jbuilder 图标。由于这是您首次使用 JBuilder,您需要输入激活密钥:

Image

请填写字段 NomSociété。 点击 [Ajouter] 输入您的激活密钥。请注意,该密钥应已通过电子邮件发送给您;若您遗失了密钥,可前往您下载 JBuilder 4 的网址获取(参见安装开始部分)。您的激活密钥被接受后,系统将再次提醒您许可条款:

Image

输入 OK 后,您将返回之前见过的信息输入窗口:

Image

输入 OK 即可完成此步骤(该步骤仅在首次运行时执行)。随后将显示 JBuilder 的开发环境:

Image

退出 JBuilder,现在开始安装文档。该过程将安装若干文件,这些文件将用于 JBuilder 的帮助文档(目前该帮助文档尚不完整)。 返回您解压 JBuilder 的 tar.gz 文件的目录。安装程序位于 docs 目录中。进入该目录:

[cd docs]
[ls -l]
-rw-r--r--    1 nobody   nobody       1128 oct 10  2000 deploy.txt
-rwxr-xr-x    1 nobody   nobody   40497874 oct 10  2000 doc_install.bin
drwxr-xr-x    2 nobody   nobody       4096 oct 10  2000 images
-rw-r--r--    1 nobody   nobody       9210 oct 10  2000 index.html
-rw-r--r--    1 nobody   nobody      23779 oct 10  2000 license.txt
-rw-r--r--    1 nobody   nobody      75739 oct 10  2000 release_notes.html
-rw-r--r--    1 nobody   nobody      31902 oct 10  2000 whatsnew.html

安装文件为 doc_install.bin,但不能直接运行。若直接运行,安装将失败且原因不明。 请使用浏览器阅读文件 index.html,以获取安装步骤的详细说明。安装程序需要一个 Java 虚拟机,即名为 java 的程序,该程序必须位于您机器的 PATH 目录中。 需要提醒的是,PATH 是一个 Unix 变量,其值(如 rep1:rep2:...:repn 所示)指明了在搜索可执行文件时需要遍历的目录(repi)。 在此,安装程序要求运行名为 java 的程序。 根据您在不同位置安装的 Java 虚拟机数量,您可能拥有该程序的多个版本。出于合理性考虑,我们将使用 Jbuilder4 提供的版本,该版本位于 /opt/jbuilder4/jdk1.3/bin 目录下。要将该目录添加到 PATH 变量中:

[echo $PATH]
/usr/local/sbin:/usr/sbin:/sbin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin
[export PATH=/opt/jbuilder4/jdk1.3/bin/:$PATH]
[echo $PATH]
/opt/jbuilder4/jdk1.3/bin/:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin

现在我们可以开始安装文档了:

[./doc_install.bin]
Preparing to install...

接下来将进行图形化安装:

Image

该过程与JBuilder Foundation的安装非常相似,因此我们不再赘述。但有一个界面不容错过:

Image

安装程序通常会自动检测到您安装 JBuilder Foundation 的位置。因此,除非检测结果有误,否则请勿更改默认设置。

文档安装完成后,我们就可以进行新的JBuilder测试了。按照上文所述启动应用程序。进入JBuilder后,选择“帮助/帮助主题”选项。在左侧窗格中,点击链接 Tutoriels

Image

JBuilder附带了一套教程,可帮助您入门Java编程。下文我们将按照上述“构建应用程序”教程进行操作。

Image

在 JBuilder 中,选择“文件/新建项目”。向导将显示 3 个界面:

Image

我们将创建一个标题为“大家好”的简单窗口。我们将该项目命名为 coucou。 该示例已由 root 执行。随后 JBuilder 建议将所有项目整合到 root 连接目录下的 jbproject 目录中。 项目 coucou 将被放置在 jbproject 下的 coucou 目录中(“项目目录名称”字段)。请执行 [suivant]。

Image

第二个屏幕是项目中各路径的汇总。无需进行任何修改。执行 [suivant]。

Image

第三个屏幕要求您自定义项目。填写完后执行 [Terminer]。

现在请执行 Fichier/Nouveau

Image

选择 Application 并执行 OK。一个新的向导将显示两个界面:

Image

字段 paquet 用于填写项目名称。类字段要求输入 nom,该值将作为项目的主类名称。请使用上述名称并将其设为 [suivant]:

Image

我们的应用程序包含一个用于应用程序窗口的第二个 Java 类。请为该类命名。字段 Titre 是您希望为该窗口设置的标题。框架 options 提供了窗口的相关选项。 请全部分别设置为 [Terminer]。随后您将返回 JBuilder 开发环境,此时系统已根据您为项目提供的信息进行了更新:

Image

在左上角的窗口(1)中,显示了构成您项目的文件列表。其中特别包含两个我们刚刚命名的类:.java。在右侧窗口(2)中,显示了类 coucouCadre 的 Java 代码。 在左下角的窗口中,显示了项目的结构(类、方法、属性)。项目已准备就绪,可以运行。点击上方的按钮 4,或选择“运行”>“运行项目”,或直接运行 F9。您应该会看到以下窗口:

Image

点击选项 Aide,您应该能看到之前在创建向导中输入的信息。我们不再深入讲解。现在是时候开始学习教程了。

在结束之前,让我们简单演示一下:您无需使用 JBuilder 及其图形界面,而是可以直接使用它自带并放置在 /opt/jbuilder4/jdk1.3 目录下的 JDK。请编译以下 essai1.java 文件:

import java.io.*;

public class essai1{
    public static void main(String args[]){
        System.out.println("coucou");
        System.exit(0);
    }
}

使用JBuilder的JDK进行编译并运行:

[ls -l *.java]
-rw-r--r--    1 root     root          135 mai  9 21:57 essai1.java
[/opt/jbuilder4/jdk1.3/bin/javac essai1.java]
[/opt/jbuilder4/jdk1.3/bin/java essai1]
coucou