About My Editor 2 Afritxia 01.13.2003
引言2
这篇文章并不太适合Java高手和刚要开始学Java的人看.如果你刚弄清楚Java编程是怎么回事,并且想用Java提供swing组件做一些简单的程序,以此来巩固对Java编程学习的话,那你算是找对了.我会披露swing组件中的一些鲜为人知的方法.希望这几篇文章能够成为你在学习Java程序设计道路中的一块铺路石,助你顺利攀到Java程序设计颠峰.
本篇话题: 文本编辑区设计.
我的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)