About My Editor (2)

类别:Java 点击:0 评论:0 推荐:


 About My Editor 2    Afritxia 01.13.2003

 引言2

    这篇文章并不太适合Java高手和刚要开始学Java的人看.如果你刚弄清楚Java编程是怎么回事,并且想用Java提供swing组件做一些简单的程序,以此来巩固对Java编程学习的话,那你算是找对了.我会披露swing组件中的一些鲜为人知的方法.希望这几篇文章能够成为你在学习Java程序设计道路中的一块铺路石,助你顺利攀到Java程序设计颠峰.
    [email protected]

    本篇话题: 文本编辑区设计.

    我的JMDEditor的编辑区使用的是非常简单的JTextArea组件.说它简单是因为它已经们实现了很多常用的功能:Cut,Copy,Paste,Select,SelectAll...甚至还可以设置被选取文本的背景色.所以我们并不用费心去写这些功能.

    但是,要做一个象样的记事本程序,光有这点功能显然是不够的.看看JDK自带的记事本程序,就连那个还有多次"撤消"与"重做"的功能呢.可它是怎样实现的呢?做了一大堆的AbstractAction类的派生类,其中的UndoAction与RedoAction就是这样来的:

 class UndoAction extends AbstractAction
 {
     public UndoAction(){
     }

     public void actionPerformed(ActionEvent e){
         // Action Code
         // 执行撤消操作
     }

     public void update(){
         // Update Code
         // 如果当前文本无法再进行撤消,则菜单中的"Undo"就不能被选择了
     }
 }

当然,还要有这些东西才能成事:

 JTextArea editor=new JTextArea();
 UndoableEditListener undoHandler=new UndoHandler(); // ??
 UndoManager undo=new UndoManager(); // 撤消管理器?
 UndoAction undoAction=new UndoAction();
 editor.getDocument().addUndoableEditListener(undoHandler);

 class UndoHandler implements UndoableEditListener // ?
 {
   public void undoableEditHappened(UndoableEditEvent e){
     undo.addEdit(e.getEdit());
        // ...
   }
 }

 JMenuItem undoMenuItem=new JMenuItem("Undo");
 undoMenuItem.addActionListener(undoAction);

 try{ // UndoAction中的撤消操作代码
     undo.undo();
 }catch(CannotUndoException ex){
     // Throws Exception
 }

我已经乱了!虽然功能比较完善,但是很容易就会让象我一样的初学者晕头转向.所以我千方百计的简化了此操作.

 JTextArea editor=new JTextArea();
 UndoManager undo=new UndoManager(); // 撤消管理器?
 undo.setLimit(5); // 5步撤消
 editor.getDocument().addUndoableEditListener(new UndoableEditListener(){
     public void undoableEditHappened(UndoableEditEvent e) {
         undo.addEdit(e.getEdit());
     }
 });

 JMenuItem undoMenuItem=new JMenuItem("Undo");
 undoMenuItem.addActionListener(...);

 public void actionPerformed(ActionEvent e){
     String cmd=e.getActionCommand();
     // ...
     if(cmd.equals("Undo"))
         try{
      undo.undo(); // Call undo.redo() if you need redo
         }catch(CannotUndoException ex){
         }
     // ...
 }

其实看起来也没简化多少.不过我省掉了UndoableEditListener,这样看起来就没有那么多的弯弯绕了.(更简单的方式?目前我是找不到了)

    作为一个程序员,在进行编码时经常会遇到编译器给出的错误提示:

 Error! ... ...
        ... ... (17)

最后给出的是错误的所在行.那么我要做的就是将光标移到文件第一行,然后一下下的数出17行来,再然后解决问题.可是如果错误是在第1234行怎么办?还用土办法?那无异于徒步登月!最好来个行列显示功能.起初,我写了一个行列显示的算法.那不值一提,因为随着文本中的字数渐多时,这个算法几乎是以死机的方式运行的.我想JTextArea中应该有这样的方法,可是我寻觅了大半天也是一无所获.最后,所有的嫌疑都被归到

 getLineOfOffset(int) 和 getLineStartOffset(int)

两个函数身上.这两个是什么意思?...看来,只有象搭积木一样把他们搭来看看了:

// import javax.swing.event.*;

 JTextArea editor=new JTextArea();
 editor.addCaretListener(new CaretListener(){
     public void caretUpdate(CaretEvent e){
         int dot=e.getDot();
         int ln, col;
         ln=col=0;
         try{
             ln=editor.getLineOfOffset(dot);
             col=dot-editor.getLineStartOffset(ln);
             System.out.println("["+(ln+1)+","+(col+1)+"]");
         }catch (BadLocationException Ex){
         }
     }
 });

至于getLineOfOffset(int)与getLineStartOffset(int),是个什么地噶活:

// 摘录自: SUN Microsystem jdk1.3.1 / src.jar / JTextArea.java

 public int getLineOfOffset(int offset) throws BadLocationException {
     Document doc = getDocument();
     if (offset < 0) {
         throw new BadLocationException("Can't translate offset to line", -1);
     } else if (offset > doc.getLength()) {
         throw new BadLocationException("Can't translate offset to line", doc.getLength()+1);
     } else {
         Element map = getDocument().getDefaultRootElement();
         return map.getElementIndex(offset);
     }
 }

 public int getLineStartOffset(int line) throws BadLocationException {
     Element map = getDocument().getDefaultRootElement();
     if (line < 0) {
         throw new BadLocationException("Negative line", -1);
     } else if (line >= map.getElementCount()) {
         throw new BadLocationException("No such line", getDocument().getLength()+1);
     } else {
         Element lineElem = map.getElement(line);
         return lineElem.getStartOffset();
     }
 }

恩!大大地好!可以把他们塞到JEditorPane,JTextPane里去,继续效忠我们Java爱好者.没看懂?各位只管拿去改改随便用就成了.

    本篇最后登场的是一个重量级话题:查找与替换

    首先,要做一个查找与替换对话框.它继承自JDialog类,并且是可以和主窗体并行的.

 public class FindDlg extends JDialog
 {
     public FindDlg(JFrame f){
         super(f, "Find and Replace.", false); // 用false就能并行
         // ... ...
     }
     // Find and Replace Code ...
 }

然后就是最重要的查找与替换功能的实现了:

// KEY: Find function //////////////////////////////////////////////////////////
// Algorithm is ideological: From 'pos' location, cut out 'findStrLen' character
// and 'findStr' to compare. If be identical to return, it is different and con-
// -tinued to cut out 'findStrLen' character from next location compare with
// 'findStr'. editTxtAra: The text area that has been sought. findStr: Find Str-
// -ing. direction: Find direction.

 public boolean find(JTextArea editTxtAra, String findStr,
                     int direction, boolean checkCase){
     if(findStr.equals("")) return(false);
     pos=editTxtAra.getSelectionEnd();
     int findStrLen=findStr.length();
     int editTxtAraLen=editTxtAra.getText().length();
     String temp="";
     if(direction==-1) pos=editTxtAra.getSelectionStart()-1;
     while(pos>=0&&pos<editTxtAraLen){
         try{
             temp=editTxtAra.getText(pos, findStrLen);
             if(checkCase&&(temp.compareToIgnoreCase(findStr)==0)
                 ||temp.equals(findStr)){
                 editTxtAra.select(pos, pos+findStrLen);
                 return(true);
             }
         }catch(Exception e){
  }
  pos+=direction;
     }
     return(false);
 }

// Why return a boolean value ?

// KEY: Replace function ///////////////////////////////////////////////////////
// Algorithm is ideological: If exist selected text, replace it.
// editTxtAra: The text area that has been sought.
// replaceStr: Use 'replaceStr' replace selection.

 private void replace(JTextArea editTxtAra, String replaceStr){
     if(editTxtAra.getSelectionStart()!=editTxtAra.getSelectionEnd())
         editTxtAra.replaceSelection(replaceStr);
 }

// KEY: Replace function ///////////////////////////////////////////////////////
// Algorithm is ideological: Circulate to seek replacement.
// editTxtAra: The text area that has been sought.
// findStr: Find String.
// replaceStr: Use 'replaceStr' replace selection.

// Why function find return a boolean value ? Are you see ?

 private void replaceAll(JTextArea editTxtAra, String findStr,
     String replaceStr, boolean checkCase){
     if(findStr.equals("")) return;
     int i;
     editTxtAra.select(0, 0);
     for(i=0; find(editTxtAra, findStr, +1, checkCase); ++i) // Are you see ?!
         replace(editTxtAra, replaceStr);
     JOptionPane.showMessageDialog(FindDlg.this,
         "Replaced "+i+" occurence(s) in this file.",
         "INFORMATION",
         JOptionPane.INFORMATION_MESSAGE);
 }

// 别怪我的E文不正确,只怪现在的翻译软件都是直来直去的(注释没看懂?别急!翻译软件能看懂.

// 按理说翻译软件是可以再直译回原文的...不成?!...那可就好玩儿了).

我并不想解释我的算法中的每一句话到底是什么意思,因为那是属于算法与数据结构的范畴,非计算机专业的Java爱好者恐怕不会在意什么算法,而且这也离我的文章的主题远了点.不过我还是很希望能有人跟我讨论一下这个算法.(如果你是Java高手,你应该发现这里的替换方法与之前的撤消联起来有点毛病,我还不知道怎么解决)

    每当查找完事以后,应该让JTextArea的对象选中一段文本表明已经找到.但是结果是不行!可以找到文本,但无法选中.我用一般的requestFocus()就是这个结果.后来我用的是比request生硬的多的grab, grabFocus(),这才解决了文本无法选中的问题.

    一切查找工作都完事了(?),总觉得少了点什么东西.是什么呢?没有默认键(就是对话框刚一出现就被(也永远被)选中的那个按钮).我试了JButton类的setDefaultButton(boolean)的方法,可是这不是我期望的那种效果.不过我还是找到了解决方法:

 JDialog dlg=new JDialog(...);
 JButton  OK=new JButton("OK");
 dlg.getContentPane().setLayout(new FlowLayout());
 dlg.getContentPane().add(OK);
 dlg.getRootPane().setDefaultButton(OK); // 默认键
 dlg.setSize(480, 320);
 dlg.show();

    搞定关键字高亮显示?要是搞定了我肯定会告诉各位的.那是个很难的课题.

    要继续写下去吗? (下次是文件I/O)

本文地址:http://com.8s8s.com/it/it18047.htm