天天看點

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>