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