天天看点

About My Editor (2)

<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

 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)

<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>