明辉手游网中心:是一个免费提供流行视频软件教程、在线学习分享的学习平台!

JBuilder 9 开发一个文本编辑器

[摘要]一、概述   文本编辑器是一种最常用的应用程序,下面我们利用Jbuilder 9集成开发环境,用java语言实现一个简单的文本编辑器。该文本编辑器具有读出、写入、编辑文本文件,可以设定文字颜色、字形和编辑区域背景颜色等基本功能。   我们首先通过Jbuilder 9项目向导和应用向导创建项目,然后...
一、概述

  文本编辑器是一种最常用的应用程序,下面我们利用Jbuilder 9集成开发环境,用java语言实现一个简单的文本编辑器。该文本编辑器具有读出、写入、编辑文本文件,可以设定文字颜色、字形和编辑区域背景颜色等基本功能。

  我们首先通过Jbuilder 9项目向导和应用向导创建项目,然后应用可视化设计工具,修改UI设计,连接事件,编辑源码以及常用控件和任务诸如菜单项、工具条、文本区域和系统事件等常用控件和任务的处理。涉及到具体技术有:

   用JFileChooser 对话框让用户选择文本文件。

   用JtextArea读、写和处理文本文件中的文字。

   设置前景色和背景色。

   用dbSwing FontChooser对话框设置字型。

   在状态栏和窗口标题栏显示信息。

   手工添加处理UI事件的代码 。

   通过将代码放在一个可被菜单项和按钮两个事件处理器调用的新的"帮助"方法中, 使得菜单项和按钮执行相同的代码。

   给JtextArea控件增加一个右击菜单。

   保持对文件的位置以及文件是否活动过的跟踪,展示对文件 新建, 文件 打开, 文件 保存, 文件 另存为,编辑和退出等逻辑的处理。

   将"Text Editor" 应用程序展开为JAR 文件。

  二、开发文本编辑器java源程序说明

  文本编辑器程序包含三个java 源程序即TextEditFrame.java、TextEditclass.java 和TextEditFrame_AboutBox.java 程序,下面将分别介绍如下:

  1、TextEditFrame.java的源程序代码(节选部分):

  package texteditor;
  //TextEditFrame.java
  import java.awt.*;//导入类
  import java.awt.event.*;
  import javax.swing.*;
  import com.borland.dbswing.*;
  import java.io.*;
  import javax.swing.text.*;
  import javax.swing.event.*;
  public class TextEditFrame extends JFrame {
  IntlSwingSupport intlSwingSupport1 = new IntlSwingSupport();
  //Swing 控件互联网化:即本地化应用程序,需要添加一行代码以便Swing 控件JfileChooser //和JcolorChooser出现在程序运行的语言中
  JPanel contentPane; //设置内容窗(contentPane)的JPanel控件
  JMenuBar menuBar1 = new JMenuBar();//创建菜单条并加入到框架窗体中
  JMenu menuFile = new JMenu();//创建File菜单和相应的菜单项
  JMenuItem menuFileExit = new JMenuItem();
  JMenu menuHelp = new JMenu();//创建Help菜单和相应的菜单项
  JMenuItem menuHelpAbout = new JMenuItem();
  JToolBar toolBar = new JToolBar();//创建工具条组件
  JButton jButton1 = new JButton();//创建按钮组件
  JButton jButton2 = new JButton();
  JButton jButton3 = new JButton();
  ImageIcon image1;//定义图标
  ImageIcon image2;
  ImageIcon image3;
  JLabel statusBar = new JLabel();//创建标签组件
  BorderLayout borderLayout1 = new BorderLayout();//创建BorderLayout 布局器
  JScrollPane jScrollPane1 = new JScrollPane();//创建滚动窗控件
  JTextArea jTextArea1 = new JTextArea();//创建多行文本域组件
  JMenuItem jMenuItem1 = new JMenuItem();//创建菜单项
  JMenuItem jMenuItem2 = new JMenuItem();
  JMenuItem jMenuItem3 = new JMenuItem();
  JMenuItem jMenuItem4 = new JMenuItem();
  FontChooser fontChooser1 = new FontChooser();//创建字型选择对话框
  JMenu jMenu1 = new JMenu();
  JMenuItem jMenuItem5 = new JMenuItem();
  JMenuItem jMenuItem6 = new JMenuItem();
  JMenuItem jMenuItem7 = new JMenuItem();
  JFileChooser jFileChooser1 = new JFileChooser();//创建文本选择对话框
  String currFileName = null; // Full path with filename. null means new/untitled.
  boolean dirty = false;
  Document document1; //文本
  DBTextDataBinder dBTextDataBinder1 = new DBTextDataBinder();
  // True means modified text.
  //构造架框
  public TextEditFrame() {
  enableEvents(AWTEvent.WINDOW_EVENT_MASK);
  try {
  jbInit();
  updateCaption();
  }
  catch(Exception e) {
  e.printStackTrace();
  }
  }
  //组件初始化
  private void jbInit() throws Exception {
  //三个工具栏按钮图标
  image1 = new ImageIcon(TextEditFrame.class.getResource("openFile.gif"));
  image2 = new ImageIcon(TextEditFrame.class.getResource("closeFile.gif"));
  image3 = new ImageIcon(TextEditFrame.class.getResource("help.gif"));
  contentPane = (JPanel) this.getContentPane();//内容创格
  document1 = jTextArea1.getDocument();//多行文本域文档
  contentPane.setLayout(borderLayout1);//borderLayout布局器
  this.setSize(new Dimension(400, 300));//窗口大小
  this.setTitle("Text Editor");//窗口标题
  statusBar.setText(" ");
  menuFile.setText("File");
  menuFileExit.setText("Exit");
  menuFileExit.addActionListener(new TextEditFrame_menuFileExit_ActionAdapter (this));
  //添加事件监听器
  menuHelp.setText("Help");
  menuHelpAbout.setText("About");
  menuHelpAbout.addActionListener(new TextEditFrame_menuHelpAbout_ActionAdapter (this));
  jButton1.setIcon(image1);//设置三个工具栏按钮图标,添加事件监听器
  jButton1.addActionListener(new TextEditFrame_jButton1_actionAdapter(this));
  jButton1.setToolTipText("Open File");
  jButton2.setIcon(image2);
  jButton2.addActionListener(new TextEditFrame_jButton2_actionAdapter(this));
  jButton2.setToolTipText("Close File");
  jButton3.setIcon(image3);
  jButton3.addActionListener(new TextEditFrame_jButton3_actionAdapter(this));
  jButton3.setToolTipText("About");
  jTextArea1.setLineWrap(true);
  jTextArea1.setWrapStyleWord(true);
  jTextArea1.setBackground(Color.white);
  jMenuItem1.setText("New");//设置菜单,添加事件监听器
  jMenuItem1.addActionListener(new TextEditFrame_jMenuItem1_actionAdapter(this));
  jMenuItem2.setText("Open");
  jMenuItem2.addActionListener(new TextEditFrame_jMenuItem2_actionAdapter(this));
  jMenuItem3.setText("Save");
  jMenuItem3.addActionListener(new TextEditFrame_jMenuItem3_actionAdapter(this));
  jMenuItem4.setText("Save As");
  jMenuItem4.addActionListener(new TextEditFrame_jMenuItem4_actionAdapter(this));
  fontChooser1.setFrame(this);
  fontChooser1.setTitle("Font");
  jMenu1.setText("Edit");
  jMenuItem5.setText("Font");
  jMenuItem5.addActionListener(new TextEditFrame_jMenuItem5_actionAdapter(this));
  jMenuItem6.setText("Foreground Color");
  jMenuItem6.addActionListener(new TextEditFrame_jMenuItem6_actionAdapter(this));
  jMenuItem7.setText("Background Color");
  jMenuItem7.addActionListener(new TextEditFrame_jMenuItem7_actionAdapter(this));
  document1.addDocumentListener(new TextEditFrame_document1_documentAdapter(this));
  dBTextDataBinder1.setJTextComponent(jTextArea1);
  //Turn off right-click file Open... menu item.
  dBTextDataBinder1.setEnableFileLoading(false);
  //Turn off right-click file Save... menu item.
  dBTextDataBinder1.setEnableFileSaving(false);
  toolBar.add(jButton1);//工具组件添加按钮
  toolBar.add(jButton2);
  toolBar.add(jButton3);
  menuFile.add(jMenuItem1);//菜单组件添加菜单项
  menuFile.add(jMenuItem2);
  menuFile.add(jMenuItem3);
  menuFile.add(jMenuItem4);
  menuFile.addSeparator();//采单组件添加分隔线
  menuFile.add(menuFileExit);
  menuHelp.add(menuHelpAbout);
  menuBar1.add(menuFile);
  menuBar1.add(jMenu1);
  menuBar1.add(menuHelp);
  this.setJMenuBar(menuBar1);
  contentPane.add(toolBar, BorderLayout.NORTH);
  //内容窗设置borderLayout布局器
  contentPane.add(statusBar, BorderLayout.SOUTH);
  contentPane.add(jScrollPane1, BorderLayout.CENTER);
  jScrollPane1.getViewport().add(jTextArea1, null);
  jMenu1.add(jMenuItem5);
  jMenu1.add(jMenuItem6);
  jMenu1.add(jMenuItem7);
  }
  // Display the About box.
  void helpAbout() {
  TextEditFrame_AboutBox dlg = new TextEditFrame_AboutBox(this);
  Dimension dlgSize = dlg.getPreferredSize();
  Dimension frmSize = getSize();
  Point loc = getLocation();
  dlg.setLocation((frmSize.width - dlgSize.width) / 2 + loc.x, (frmSize.height - dlgSize.height) / 2 + loc.y);
  dlg.setModal(true);
  dlg.show();
  }
  .........
  .........

  TextEditFrame.java 是实现文本编辑器的主要程序,它有下面6点编程技巧说明:

  1) 制作一个完全充满用户界面顶部菜单栏和底部状态栏之间区域的文本区

  主用户界面容器的布局管理器需要采用边界布局(Borderlayout)。在主容器中,含有一个叫做内容窗(contentPane)的JPanel 控件,被改变成边界布局,需要做的只是在内容窗添加一个文本区控件。为此,先在内容窗添加一个滚动窗,再在滚动窗内放上文本区控件(jTextArea)。滚动窗提供一个带滚动棒(JScollPane)的文本区。

  一个边界布局的容器被分成五个区域:北、南、东、西、中。每个区域只能有一个控件,所以最多可有五个控件(注:含有多个控件的面板被认为是一个控件)。放进中心区域的控件完全占满该容器控件,不被含有控件的任何其他区域所占据。例如,在本例中,工具栏占据北区(顶),状态栏占据南区(低步),由于东西两个区域没有安排控件,这样滚动窗控件占据中心区域并扩展到容器的左(西)右(东)的边缘。

  2) 创建菜单File (包含New、Open、Save、Save as 和Exit菜单项),菜单Edit{包含Font(字体)、Foreground(前景色)和Background(背景色)菜单项} 和菜单Help (包含About帮助说明)

  ①菜单Edit的Font(字体)、Foreground(前景色)和Background(背景色)菜单项:

   添加字型选择对话框

  给菜单挂上事件,从 Edit Font 菜单项开始,该菜单将引出一个FontChooser (字型选择)对话框。
  给FontChooser附加一个菜单项事件(源程序)如下:


  void jMenuItem5_actionPerformed(ActionEvent e) {
  // Handle the "Edit Font" menu item
  // Pick up the existing font from the text area
  // and put it into the FontChooser before showing
  // the FontChooser, so that we are editing the
  // existing / previous font.

  fontChooser1.setSelectedFont(jTextArea1.getFont());

  // Obtain the new Font from the FontChooser.
  // First test the return value of showDialog() to
  // see if the user pressed OK.
  if (fontChooser1.showDialog()) {
  // Set the font of jTextArea1 to the font
  // the user selected before pressing the OK button
  jTextArea1.setFont(fontChooser1.getSelectedFont());
  }
  //repaints menu after item is selected
  this.repaint();
  //Repaints text properly if some text is highlighted when font is changed.
  jTextArea1.repaint();
  }

  给JcolorChooser(颜色选择)附加一个菜单项事件

  创建Edit Foreground and Edit Background两个菜单事件,并将它们与Swing中的JcolorChooser对话控件连接起来。

  void jMenuItem6_actionPerformed(ActionEvent e) {
  // Handle the "Foreground Color" menu item
  Color color = JColorChooser.showDialog(this,"Foreground Color",jTextArea1.getForeground());
  if (color != null) {
  jTextArea1.setForeground(color);
  }
  //repaints menu after item is selected
  this.repaint();
  }
  void jMenuItem7_actionPerformed(ActionEvent e) {
  // Handle the "Background Color" menu item
  Color color = JColorChooser.showDialog(this,"Background
  Color",jTextArea1.getBackground());
  if (color != null) {
  jTextArea1.setBackground(color);
  }
  //repaints menu after item is selected
  this.repaint();
  }

  ②菜单File 的New、Open、Save、Save as 和Exit菜单项:

   添加测试文件是否被修改的代码

  程序需要保持跟踪文件被生成、打开、或保存之后是否被修改过("脏的"),这样当关闭文件或退出程序时就可以提示问用户是否要保存操作。为此增加一个称作dirty的布尔变量。

  在源代码中添加下列okToAbandon()方法,可将这个新方法紧放在saveAsFile()方法之后:

  // Check if file is dirty.
  // If so get user to make a "Save? yes/no/cancel" decision.
  boolean okToAbandon() {
  int value = JOptionPane.showConfirmDialog(this, "Save changes?","Text Edit", JOptionPane.YES_NO_CANCEL_OPTION);
  switch (value) {
  case JOptionPane.YES_OPTION:
   // yes, please save changes
   return saveFile();
  case JOptionPane.NO_OPTION:
   // no, abandon edits
   // i.e. return true without saving
   return true;
  case JOptionPane.CANCEL_OPTION:
  default:
   // cancel
   return false;
  }
  }

  将在随后完成的上面方法当用户选择 File New, File Open, 或 File Exit时就被调用。 这个方法的目的是测试文本是否需要保存(是否动过)。若文本动过,这个方法就用Yes, No, Cancel消息对话问用户是否保存。这个方法在用户点选Yes按钮时,也调用saveFile()。若这个方法返回的布尔值是true(真),则表明可以退出当前文件,因为文件是干净的或用户点选了Yes或No按钮。如果返回值是false(假),意味着用户点选了Cancel。实际检查文件是否被改变的代码在随后的步骤中添加。目前这个方法总认为文件是不干净的,即使文字根本没被动过。随后要添加一个当用户在文本区域输入文字时就将dirty变量设置为true的方法,并在okToAbandon()方法的头部增加测试dirty变量的代码。

   添加一个清除文本区的菜单事件处理器

  将File New菜单项与清除文本区的事件处理器挂起钩来。

  void jMenuItem1_actionPerformed(ActionEvent e) {
  // Handle the File New menu item.
  if (okToAbandon()) {
  // clears the text of the TextArea
  jTextArea1.setText("");
  // clear the current filename and set the file as clean:
  currFileName = null;
  dirty = false;
  updateCaption();
  }
  }

   添加一个文件选择对话框

  将File Open菜单项与提供给用户一个文件选择对话框的JfileChooser控件的事件处理器挂起钩来。如果用户选择了一个文件,按下了OK按钮,这个事件处理器打开那个文本文件并把文字放入JTextArea(即文本区)。

  void jMenuItem2_actionPerformed(ActionEvent e) {
  //Handle the File Open menu item.
  if (!okToAbandon()) {
  return;
  }
  // Use the OPEN version of the dialog, test return for Approve/Cancel
  if (JFileChooser.APPROVE_OPTION == jFileChooser1.showOpenDialog(this)) {
  // Call openFile to attempt to load the text from file into
  TextArea
  openFile(jFileChooser1.getSelectedFile().getPath());
  }
  this.repaint();
  }

   添加从文件中读出文字的代码

  添加从选定的文件中将文字实际读到文本区JTextArea的代码。

  首先需要在用户类中增加一个新的方法执行实际打开文件的操作,这个方法叫作openFile()。

  // Open named file; read text from file into jTextArea1; report to statusBar.
  void openFile(String fileName)
  {
  try
  {
  // Open a file of the given name.
  File file = new File(fileName);
  // Get the size of the opened file.
  int size = (int)file.length();
  // Set to zero a counter for counting the number of
  // characters that have been read from the file.
  int chars_read = 0;
  // Create an input reader based on the file, so we can read its data.
  // FileReader handles international character encoding conversions.
  FileReader in = new FileReader(file);
  // Create a character array of the size of the file,
  // to use as a data buffer, into which we will read
  // the text data.
  char[] data = new char[size];
  // Read all available characters into the buffer.
  while(in.ready()) {
   // Increment the count for each character read,
   // and accumulate them in the data buffer.
   chars_read += in.read(data, chars_read, size - chars_read);
  }
  in.close();
  // Create a temporary string containing the data,
  // and set the string into the JTextArea.
  jTextArea1.setText(new String(data, 0, chars_read));
  // Display the name of the opened directory+file in the statusBar.
  statusBar.setText("Opened "+fileName);
  }
  catch (IOException e)
  {
  statusBar.setText("Error opening "+fileName);
  }
  }

  替换先前所说的File Open 事件处理器中的 if() 语句:

  // Display the name of the opened directory+file in the statusBar.
  statusBar.setText("Opened "+jFileChooser1.getSelectedFile().getPath());
  // Code will need to go here to actually load text
  // from file into JTextArea.
  with this new openFile() method instead, using the concatenated
  Directory and File name.
  // Call openFile to attempt to load the text from file into JTextArea
  openFile(jFileChooser1.getSelectedFile().getPath());
  //repaints menu after item is selected
  this.repaint();

   添加一个保存文件的菜单项

  在选择File Save 和 File Save as时,将文件写回到磁盘的代码。

  为此,需要增加一个含有被打开文件名字的字符串例程变量和增加将文字写回到文件或另一个文件的方法。

  创建下列可从File Save事件处理器调用的方法saveFile()。可将其放在openFile()方法之后。这个方法在保存时也将文件名写到状态栏。           

  // Save current file; handle not yet having a filename; report to statusBar.
  boolean saveFile() {
  // Handle the case where we don't have a file name yet.
  if (currFileName == null) {
  return saveAsFile();
  }
  try
  {
  // Open a file of the current name.
  File file = new File (currFileName);
  // Create an output writer that will write to that file.
  // FileWriter handles international characters encoding conversions.
  FileWriter out = new FileWriter(file);
  String text = jTextArea1.getText();
  out.write(text);
  out.close();
  this.dirty = false;
  // Display the name of the saved directory+file in the statusBar.
  statusBar.setText("Saved to " + currFileName);
  return true;
  }
  catch (IOException e) {
  statusBar.setText("Error saving "+currFileName);
  }
  return false;
  }

  创建没有当前文件名时,从saveFile()方法中调用的saveAsFile()方法。它也可以从File Save As 菜单调用。将下列代码紧放在saveFile()方法之后:

  // Save current file, asking user for new destination name.
  // Report to statuBar.
  boolean saveAsFile() {
  // Use the SAVE version of the dialog, test return for Approve/Cancel
  if (JFileChooser.APPROVE_OPTION == jFileChooser1.showSaveDialog(this)) {
  // Set the current file name to the user's selection,
  // then do a regular saveFile
  currFileName = jFileChooser1.getSelectedFile().getPath();
  //repaints menu after item is selected
  this.repaint();
  return saveFile();
  }
  else {
  this.repaint();
  return false;
  }
  }

  菜单File Exit, 在退出应用程序的代码如下:

  //File Exit action performed
  public void jMenuFileExit_actionPerformed(ActionEvent e) {
  if (okToAbandon()) {
  System.exit(0);
  }
  }

  3) 激活工具栏按钮

  如果在应用向导中选择了Generate Toolbar(生成工具栏)选项,则Jbuilder就生成通常带有三个JButton 按钮(OPen File、Save File 和About) 控件且有图标显示的JtoolBar(工具栏)代码。要做的就是给每个按钮的标称指定文字和指定工具提示文字,并为每个按钮创建一个actionPerformed()事件,用户从每个按钮actionPerformed()事件中调用相应的事件处理方法。

   指定按钮工具的提示文字

   对应jButton1输入Open File
   对应jButton2输入Save File
   对应jButton3输入About

   创建按钮事件

   创建对应jButton1的jButton1_actionPerformed(ActionEvent e)事件并从中调用openFile()方法:

  //Handle toolbar Open button
  openFile();

   创建对应jButton2的jButton2_actionPerformed(ActionEvent e)事件并从中调用 saveFile()方法:

  //Handle toolbar Save button
  saveFile();
  创建对应jButton3的jButton3_actionPerformed(ActionEvent e)事件并从中调用helpAbout()方法:
  //Handle toolbar About button
  helpAbout();

    创建fileOpen()方法

  fileOpen()方法的目的是执行当前在File Open菜单项处理方法中的操作。即按下Open按钮和选择File Open 菜单项执行的是同样的操作,所以创建fileOpen()方法将代码复制一下即可。从File Open 菜单和Open按钮调用相同的代码。

  // Handle the File Open menu or button, invoking okToAbandon and openFile
  // as needed.
  void fileOpen() {
  if (!okToAbandon()) {
  return;
  }
  // Use the OPEN version of the dialog, test return for Approve/Cancel
  if (JFileChooser.APPROVE_OPTION == jFileChooser1.showOpenDialog(this)) {
  // Call openFile to attempt to load the text from file into TextArea
  openFile(jFileChooser1.getSelectedFile().getPath());
  }
  this.repaint();
  }

   创建saveFile()方法

  对File save菜单和save按钮再做一次同样的事情。将当前File save事件处理器中的代码收集到新的 saveFile()方法中,即可从菜单处理器也可从按钮处理器对其调用。

  // Save current file; handle not yet having a filename; report to statusBar.
  boolean saveFile() {
  // Handle the case where we don't have a file name yet.
  if (currFileName == null) {
  return saveAsFile();
  }
  try
  {
  // Open a file of the current name.
  File file = new File (currFileName);
  // Create an output writer that will write to that file.
  // FileWriter handles international characters encoding conversions.
  FileWriter out = new FileWriter(file);
  String text = jTextArea1.getText();
  out.write(text);
  out.close();
  this.dirty = false;
  // Display the name of the saved directory+file in the statusBar.
  statusBar.setText("Saved to " + currFileName);
  updateCaption();
  return true;
  }
  catch (IOException e) {
  statusBar.setText("Error saving " + currFileName);
  }
  return false;
  }

  建helpAbout()方法

  对Help About菜单和About按钮再做一次同样的事情。将当前Help About事件处理器中的代码收集到新的 helpAbout()方法中,即可从菜单处理器也可从按钮处理器对其调用。

  // Display the About box.
  void helpAbout() {
  TextEditFrame_AboutBox dlg = new TextEditFrame_AboutBox(this);
  Dimension dlgSize = dlg.getPreferredSize();
  Dimension frmSize = getSize();
  Point loc = getLocation();
  dlg.setLocation((frmSize.width - dlgSize.width) / 2 + loc.x,(frmSize.height - dlgSize.height) / 2 + loc.y);

  dlg.setModal(true);
  dlg.show();
  }


  4) 将事件处理与文本区联系起来

  将事件处理与文本区JtextArea联系起来,这样只要有输入操作用户程序就设置dirty标志为真。为此需要在JtextArea的文本模板中添加一个Swing控件DocumentListener(文本监听器)且检查插入、删除和改变等事件。

   DocumentListener被添加到 jbInit():

  document1.addDocumentListener(new TextEditFrame_document1_documentAdapter(this));

   在void document1_changedUpdate(DocumentEvent e)事件中插入下面代码:

  dirty = true;

   为document1 再创建两个事件:insertUpdate()和removeUpdate()。 在这些事件中插入在changedUpdate()事件中使用的同样的代码。

  这样只要在文本区输入任何字符都将强迫dirty标志为真。

  5) 在文本区添加一个右击弹出菜单

  控件DBTextDataBinder用来在Swing文本控件中添加一个执行诸如剪切、复制、粘贴等简单编辑任务的右击菜单。DBTextDataBinder还有一个将文件装入JtextArea和保存文件的内部动作,但不允许用户恢复显示在状态栏的装入或保存的文件名。本例中,只是添加一个DBTextDataBinder控件给jTextArea1,不使用其中的文件打开和保存操作。

  6) 在窗口的标题栏展示文件名和状态

  添加使用应用程序的标题栏显示当前文件名和当文件受"污染"时显示一个星花号的代码。为此,创建一个更新标题栏的新方法,然后从代码改变文件名或改变dirty标志的地方调用它。给这个新方法取名为updateCaption()。

   updateCaption()方法:

  // Update the title bar of the application to show the filename and its dirty state.
  void updateCaption() {
  String caption;
  if (currFileName == null) {
  // synthesize the "Untitled" name if no name yet.
  caption = "Untitled";
  }
  else {
  caption = currFileName;
  }
  // add a "*" in the caption if the file is dirty.
  if (dirty) {
  caption = "* " + caption;
  }
  caption = "Text Editor - " + caption;
  this.setTitle(caption);
  }

   从dirty标志被实际改变的每一个地方或者每当用户改变当前文件名currFileName的时候调用 updateCaption()。特别要将调用 updateCaption()语句放在下列这些地方:

  ①在TextEditFrame()构造器的try模块内紧接着调用jbInit()方法之后;

  ② 在openFile()方法的try模块的最后一行;

  ③ 在saveFile()方法try模块返回true的前一行;

  ④ 在File New 菜单处理器jMenuItem1_actionPerformed()的if模块的最后一行;

  ⑤当由于用户输入,dirty标志在干净的文件中第一次被设置为true时。每一个文本事件处理器都应该改变为:

  void document1_changedUpdate(DocumentEvent e) {
  if (!dirty) {
  dirty = true;
  updateCaption();
  }
  }
  void document1_insertUpdate(DocumentEvent e) {
  if (!dirty) {
  dirty = true;
  updateCaption();
  }
  }
  void document1_removeUpdate(DocumentEvent e) {
  if (!dirty) {
  dirty = true;
  updateCaption();
  }
  }

  2、TextEditclass.java的源程序代码:

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

  /**
  * <p>Title: TextEditor</p>
  * <p>Description: This is a study programme</p>
  * <p>Copyright: Copyright (c) 2004</p>
  * <p>Company: ghq</p>
  * @author ghq
  * @version 1.0
  */

  public class TextEditClass {
  boolean packFrame = false;

  //Construct the application
  public TextEditClass() {
  TextEditFrame frame = new TextEditFrame();
  //Validate frames that have preset sizes
  //Pack frames that have useful preferred size info, e.g. from their layout
  if (packFrame) {
   frame.pack();
  }
  else {
   frame.validate();
  }
  //Center the window
  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);
  }

  //Main method
  public static void main(String[] args) {
  try {
   UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e) {
   e.printStackTrace();
  }
  new TextEditClass();
  }
  }

  上面的这段程序主要是构建 TextEditorFrame 的主窗口和主方法入口(main ()),它有下面2点编程技巧说明:

  1) 设置外观及基调

  设计时的外观和基调

  如果已经把 Jbuilder的外观和基调从其默认值改变了,则在开始使用UI设计器之前,在Jbuilder 内让UI 设计器使用 Metal Look & Feel(金属外观和基调),也可以使用其它外观和基调,但本例中选择金属外观和基调,这时一种适合于跨平台设计的选择。

  运行时的外观和基调

  在设计器的弹出菜单或Jbuilder环境选项对话框中设置的外观和基调对运行时的用户界面(UI)没有任何影响。要强制一个运行时的外观和基调,必须在应用程序(本例即为TextEditClass.java)的类的主方法(main())中设置。

  作为默认,应用向导会在可运行类的main()方法中生成下列一行代码:

  UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

  其含义是运行时的外观采用主机系统使用的外观。

  若是想要CDE/Motif 或 Windows 式的外观则参数应改变为:

  UIManager.setLookAndFeel
  ("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
  或
  UIManager.setLookAndFeel
  ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");

  2) 窗口的定义

  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);

  3、TextEditFrame_AboutBox.java的源程序代码:

  package texteditor;
  import java.awt.*;
  import java.awt.event.*;
  import javax.swing.*;
  import javax.swing.border.*;
  /**
  * <p>Title: TextEditor</p>
  * <p>Description: This is a study programme</p>
  * <p>Copyright: Copyright (c) 2004</p>
  * <p>Company: ghq</p>
  * @author ghq
  * @version 1.0
  */
  public class TextEditFrame_AboutBox extends JDialog implements
  ActionListener {
  JPanel panel1 = new JPanel();
  JPanel panel2 = new JPanel();
  JPanel insetsPanel1 = new JPanel();
  JPanel insetsPanel2 = new JPanel();
  JPanel insetsPanel3 = new JPanel();
  JButton button1 = new JButton();
  JLabel imageControl1 = new JLabel();
  ImageIcon imageIcon;
  JLabel label1 = new JLabel();
  JLabel label2 = new JLabel();
  JLabel label3 = new JLabel();
  JLabel label4 = new JLabel();
  BorderLayout borderLayout1 = new BorderLayout();
  BorderLayout borderLayout2 = new BorderLayout();
  FlowLayout flowLayout1 = new FlowLayout();
  FlowLayout flowLayout2 = new FlowLayout();
  GridLayout gridLayout1 = new GridLayout();
  String product = "TextEditor";
  String version = "1.0";
  String copyright = "Copyright (c) 2002";
  String comments = "This is a study programme";
  //Construct the frame
  public TextEditFrame_AboutBox(Frame parent) {
  super(parent);
  enableEvents(AWTEvent.WINDOW_EVENT_MASK);
  try {
   jbInit();
  }
  catch(Exception e) {
   e.printStackTrace();
  }
  pack();
  }
  //Component initialization
  private void jbInit() throws Exception {
  //imageLabel.setIcon(new ImageIcon(TextEditFrame_AboutBox.class.getResource("[Your Image]")));
  this.setTitle("About");
  setResizable(false);
  panel1.setLayout(borderLayout1);
  panel2.setLayout(borderLayout2);
  insetsPanel1.setLayout(flowLayout1);
  insetsPanel2.setLayout(flowLayout1);
  insetsPanel2.setBorder(new EmptyBorder(10, 10, 10, 10));
  gridLayout1.setRows(4);
  gridLayout1.setColumns(1);
  label1.setText(product);
  label2.setText(version);
  label3.setText(copyright);
  label4.setText(comments);
  insetsPanel3.setLayout(gridLayout1);
  insetsPanel3.setBorder(new EmptyBorder(10, 60, 10, 10));
  button1.setText("Ok");
  button1.addActionListener(this);
  insetsPanel2.add(imageControl1, null);
  panel2.add(insetsPanel2, BorderLayout.WEST);
  this.getContentPane().add(panel1, null);
  insetsPanel3.add(label1, null);
  insetsPanel3.add(label2, null);
  insetsPanel3.add(label3, null);
  insetsPanel3.add(label4, null);
  panel2.add(insetsPanel3, BorderLayout.CENTER);
  insetsPanel1.add(button1, null);
  panel1.add(insetsPanel1, BorderLayout.SOUTH);
  panel1.add(panel2, BorderLayout.NORTH);
  }
  //Overridden so we can exit when windows is cancel
  protected void processWindowEvent(WindowEvent e) {
  if (e.getID() == WindowEvent.WINDOW_CLOSING) {
  cancel();
  }
  super.processWindowEvent(e);
  }
  void cancel() {
  dispose();
  }
  // Help About de button is used action performed
  public void actionPerformed(ActionEvent e) {
  if (e.getSource() == button1) {
  cancel();
  }
  }
  }


  说明:上面的这段程序主要是构建Help菜单的AboutBox 对话框,显示product、version 和comments 等内容。
  
  至此我们已完成文本编辑器所有的菜单及代码设计等工作,在 jbuilder9 环境下编译运行会出现如下的Text Editor窗口:



  三、从命令行运行打包程序有关说明

  首先将Text Editor应用程序装配成一个JAR文件。既然已经创建了"Text Editor" 应用程序,用户就可以使用Jbuilder9的Archive Builder(档案构筑器)将全部文件装配成一个Java Archive File(JAR,Java归档文件)。

  在从命令行运行应用程序之前,用户必须确保操作系统的PATH环境变量指向JDK jre/bin/目录,即Java的运行环境。Jbuilder9安装过程保证了Jbuilder9 知道到那里找到JDK 类文件。但是若离开了Jbuilder9环境,系统需要知道运行Java时,类文件被安装到了什么位置。如何设置PATH环境变量视用户所用操作系统而定。要从命令行运行 Text Edit程序,步骤如下:

  ①切换到命令行窗口,将路径改变到JAR文件所在的TextEditor 目录。

  ② 在命令行输入java看看Java是否在当前路径中,若在就会显示Java的使用和选项,若不在,就将PATH环境变量设到JDK的jre/bin/文件夹。对于Windows XP和NT/2000/2003系统,设置路径如下:
  set PATH=<e:><jbuilder9><jdk>jrein

  这里:

   <e:> 是驱动器;

   <jbuilder9>是 Jbuilder9目录名;

   <jdk>是Jbuilder安装时提供的JDK 目录名,例如:jbuilder9/jdk1.4/

  ③ 在命令行输入下列命令:

  java -jar TextEditor.jar

  这里:

   java ---- 运行java文件的Java工具。

   jar ---- 该选项告诉 Java VM (Java虚拟机)这是一个打包文件。

   TextEditor.jar ---- 包文件的名字。

  由于清单文件在Main-Class头中提供了运行那个类,所以在命令行末尾无需指定类名,并且由于所有的类、资源、和独立件都被包含到了装配成包的JAR文件中,所以也不需要指定类路径classpath或将Jbuilder的库文件复制到当前目录。

  注:一旦使用了-jar选项,Java运行时就会忽略任何显式给出的classpath设置。

  如果我们不是在 TextEditor 目录下运行这个JAR文件,则应使用下列Java命令:

  java -jar -classpath <full_path> <main_class_name>

  Java运行时在JAR文件中寻找启动类和应用程序使用的其它类,Java VM虚拟机使用三个搜寻路径查找文件,它们是:引导类路径、安装时扩展路径和用户类路径。