StyledText是SWT包中的一个基础组件,就像它的名字定义的那样,通过它可以显示各式各样的字体。但是StyledText没有提供Undo/Redo的功能,很是让人不爽,下面给一个简单的例子,演示在StyledText增加Undo/Redo操作,同时在这个自定义的StyledText中增加了删除选中文本、清除全部文本的动作支持。
思路很简单,就是监听文本内容更改事件,然后根据更改的内容生成Undo操作存入Undo操作栈中,在Undo操作栈中维护了两个列表UndoList和RedoList,当发出Undo操作命令的时候,从Undo操作列表中取出一个Undo操作,执行其undo()方法,然后将其放入Redo操作列表中。如果发出Redo操作命令,就从Redo操作列表中出一个Redo操作,执行其redo()方法,然后将其放入Undo操作列表中。
本篇文章只是给出了一个简单的演示示例,需要改进的地方是监听文本更改时,应该采用一定的规则策略来生成Undo操作,否则每输入或者删除一个字符都会创建一个Undo操作,这样就会产生非常多的Undo操作,并且也不符合日常使用习惯。日后有时间的话我会整理一下改进的版本发布出来。如果你有兴趣也可以研究一下。
另外由于StyledText的invokeAction()方法中Action的动作不够充足,致使无法用setKeyBinding()方法方便的将快捷键与执行动作绑定,通过重写StyledText中的invokeAction()方法可以定制某些自己的动作,完善StyledText的功能。
下面是Undo操作管理器UndoManager的代码:
1 import org.eclipse.core.commands.ExecutionException;
2 import org.eclipse.core.commands.operations.AbstractOperation;
3 import org.eclipse.core.commands.operations.IOperationHistory;
4 import org.eclipse.core.commands.operations.IUndoContext;
5 import org.eclipse.core.commands.operations.ObjectUndoContext;
6 import org.eclipse.core.commands.operations.OperationHistoryFactory;
7 import org.eclipse.core.runtime.IAdaptable;
8 import org.eclipse.core.runtime.IProgressMonitor;
9 import org.eclipse.core.runtime.IStatus;
10 import org.eclipse.core.runtime.Status;
11 import org.eclipse.swt.custom.ExtendedModifyEvent;
12 import org.eclipse.swt.custom.ExtendedModifyListener;
13 import org.eclipse.swt.custom.StyledText;
14
15 /**
16 * 管理Undo操作,用于监听文本域的文本改变事件,生成Undo操作并记录。<br>
17 *
18 * @author qujinlong
19 */
20 public class UndoManager
21 {
22 /*
23 * 用于存储历史Undo操作,每改变一次文本内容,就将构造一个Undo操作存入OperationHistory中。
24 */
25 private final IOperationHistory opHistory;
26
27 /*
28 * Undo操作上下文,一般用于在OperationHistory中查找当前文本框的Undo操作。
29 */
30 private IUndoContext undoContext = null;
31
32 /*
33 * 所要监听的需要实现Undo操作的文本框。
34 */
35 private StyledText styledText = null;
36
37 private int undoLevel = 0;
38
39 public UndoManager(int undoLevel)
40 {
41 opHistory = OperationHistoryFactory.getOperationHistory();
42
43 setMaxUndoLevel(undoLevel);
44 }
45
46 public void setMaxUndoLevel(int undoLevel)
47 {
48 this.undoLevel = Math.max(0, undoLevel);
49
50 if (isConnected())
51 opHistory.setLimit(undoContext, this.undoLevel);
52 }
53
54 public boolean isConnected()
55 {
56 return styledText != null;
57 }
58
59 /*
60 * 将Undo管理器与指定的StyledText文本框相关联。
61 */
62 public void connect(StyledText styledText)
63 {
64 if (! isConnected() && styledText != null)
65 {
66 this.styledText = styledText;
67
68 if (undoContext == null)
69 undoContext = new ObjectUndoContext(this);
70
71 opHistory.setLimit(undoContext, undoLevel);
72 opHistory.dispose(undoContext, true, true, false);
73
74 addListeners();
75 }
76 }
77
78 public void disconnect()
79 {
80 if (isConnected())
81 {
82 removeListeners();
83
84 styledText = null;
85
86 opHistory.dispose(undoContext, true, true, true);
87
88 undoContext = null;
89 }
90 }
91
92 private ExtendedModifyListener extendedModifyListener = null;
93
94 private boolean isUndoing = false;
95
96 /*
97 * 向Styled中注册监听文本改变的监听器。
98 *
99 * 如果文本改变,就构造一个Undo操作压入Undo操作栈中。
100 */
101 private void addListeners()
102 {
103 if (styledText != null)
104 {
105 extendedModifyListener = new ExtendedModifyListener() {
106 public void modifyText(ExtendedModifyEvent event)
107 {
108 if (isUndoing)
109 return;
110
111 String newText = styledText.getText().substring(event.start,
112 event.start + event.length);
113
114 UndoableOperation operation = new UndoableOperation(undoContext);
115
116 operation.set(event.start, newText, event.replacedText);
117
118 opHistory.add(operation);
119 }
120 };
121
122 styledText.addExtendedModifyListener(extendedModifyListener);
123 }
124 }
125
126 private void removeListeners()
127 {
128 if (styledText != null)
129 {
130 if (extendedModifyListener != null)
131 {
132 styledText.removeExtendedModifyListener(extendedModifyListener);
133
134 extendedModifyListener = null;
135 }
136 }
137 }
138
139 public void redo()
140 {
141 if (isConnected())
142 {
143 try
144 {
145 opHistory.redo(undoContext, null, null);
146 }
147 catch (ExecutionException ex)
148 {
149 }
150 }
151 }
152
153 public void undo()
154 {
155 if (isConnected())
156 {
157 try
158 {
159 opHistory.undo(undoContext, null, null);
160 }
161 catch (ExecutionException ex)
162 {
163 }
164 }
165 }
166
167 /*
168 * Undo操作用于记录StyledText的文本被改变时的相关数据。
169 *
170 * 比如文本框中本来的文本为111222333,如果此时选中222替换为444(用复制粘帖的方法),
171 *
172 * 则Undo操作中记录的相关数据为: startIndex = 3; newText = 444; replacedText = 222;
173 */
174 private class UndoableOperation extends AbstractOperation
175 {
176 // 记录Undo操作时,被替换文本的开始索引
177 protected int startIndex = - 1;
178
179 // 新输入的文本
180 protected String newText = null;
181
182 // 被替换掉的文本
183 protected String replacedText = null;
184
185 public UndoableOperation(IUndoContext context)
186 {
187 super("Undo-Redo");
188
189 addContext(context);
190 }
191
192 /*
193 * 设置Undo操作中要存储的相关数据。
194 */
195 public void set(int startIndex, String newText, String replacedText)
196 {
197 this.startIndex = startIndex;
198
199 this.newText = newText;
200 this.replacedText = replacedText;
201 }
202
203 /*
204 * (non-Javadoc)
205 *
206 * @see org.eclipse.core.commands.operations.AbstractOperation#undo(org.eclipse.core.runtime.IProgressMonitor,
207 * org.eclipse.core.runtime.IAdaptable)
208 */
209 public IStatus undo(IProgressMonitor monitor, IAdaptable info)
210 throws ExecutionException
211 {
212 isUndoing = true;
213 styledText.replaceTextRange(startIndex, newText.length(), replacedText);
214 isUndoing = false;
215
216 return Status.OK_STATUS;
217 }
218
219 /*
220 * (non-Javadoc)
221 *
222 * @see org.eclipse.core.commands.operations.AbstractOperation#redo(org.eclipse.core.runtime.IProgressMonitor,
223 * org.eclipse.core.runtime.IAdaptable)
224 */
225 public IStatus redo(IProgressMonitor monitor, IAdaptable info)
226 throws ExecutionException
227 {
228 isUndoing = true;
229 styledText.replaceTextRange(startIndex, replacedText.length(), newText);
230 isUndoing = false;
231
232 return Status.OK_STATUS;
233 }
234
235 /*
236 * (non-Javadoc)
237 *
238 * @see org.eclipse.core.commands.operations.AbstractOperation#execute(org.eclipse.core.runtime.IProgressMonitor,
239 * org.eclipse.core.runtime.IAdaptable)
240 */
241 public IStatus execute(IProgressMonitor monitor, IAdaptable info)
242 throws ExecutionException
243 {
244 return Status.OK_STATUS;
245 }
246 }
247 }
下面是扩展的StyledText类:
1 import org.eclipse.swt.SWT;
2 import org.eclipse.swt.custom.ST;
3 import org.eclipse.swt.custom.StyledText;
4 import org.eclipse.swt.layout.GridData;
5 import org.eclipse.swt.layout.GridLayout;
6 import org.eclipse.swt.widgets.Composite;
7 import org.eclipse.swt.widgets.Display;
8 import org.eclipse.swt.widgets.Shell;
9
10 /**
11 * 自定义的StyledText,增加了Undo、Redo和清除操作。<br>
12 *
13 * @author qujinlong
14 */
15 public class MyStyledText extends StyledText
16 {
17 /**
18 * @param parent
19 * @param style
20 */
21 public MyStyledText(Composite parent, int style)
22 {
23 super(parent, style);
24 }
25
26 /*
27 * (non-Javadoc)
28 *
29 * @see org.eclipse.swt.custom.StyledText#invokeAction(int)
30 */
31 public void invokeAction(int action)
32 {
33 // 增加一个自定义的删除操作,只有当选中文本的时候才将文本删除。
34 // 否则,ST.DELETE_NEXT会将光标所在位置的后一个字符删除。
35 if (action == ActionCode.DELETE && getSelectionCount() > 0)
36 action = ST.DELETE_NEXT;
37
38 super.invokeAction(action);
39
40 switch (action)
41 {
42 case ActionCode.UNDO:
43 undo();
44 break;
45 case ActionCode.REDO:
46 redo();
47 break;
48 case ActionCode.CLEAR:
49 clear();
50 break;
51 }
52 }
53
54 private void undo()
55 {
56 if (undoManager != null)
57 undoManager.undo();
58 }
59
60 private void redo()
61 {
62 if (undoManager != null)
63 undoManager.redo();
64 }
65
66 private void clear()
67 {
68 super.setText("");
69 }
70
71 private UndoManager undoManager = null;
72
73 /**
74 * @return Returns undoManager.
75 */
76 public UndoManager getUndoManager()
77 {
78 return undoManager;
79 }
80
81 /**
82 * @param undoManager - The undoManager to set.
83 */
84 public void setUndoManager(UndoManager undoManager)
85 {
86 this.undoManager = undoManager;
87 }
88
89 /*
90 * (non-Javadoc)
91 *
92 * @see org.eclipse.swt.widgets.Widget#dispose()
93 */
94 public void dispose()
95 {
96 if (undoManager != null)
97 undoManager.disconnect();
98
99 super.dispose();
100 }
101
102 public static class ActionCode
103 {
104 public static final int UNDO = Integer.MAX_VALUE;
105
106 public static final int REDO = UNDO - 1;
107
108 public static final int CLEAR = UNDO - 2;
109
110 public static final int DELETE = UNDO - 3;
111 }
112
113 public static void main(String[] args)
114 {
115 final Display display = Display.getDefault();
116 final Shell shell = new Shell();
117 shell.setLayout(new GridLayout());
118 shell.setSize(420, 250);
119 shell.setText("SWT Application");
120
121 MyStyledText styledText = new MyStyledText(shell, SWT.BORDER);
122 GridData gd_styledText = new GridData(SWT.FILL, SWT.CENTER, false, false);
123 gd_styledText.heightHint = 200;
124 gd_styledText.widthHint = 400;
125 styledText.setLayoutData(gd_styledText);
126
127 // Ctrl+C, Ctrl+X, Ctrl+V 都是StyledText的默认行为。
128
129 // styledText.setKeyBinding('C' | SWT.CTRL, ST.COPY);
130 // styledText.setKeyBinding('V' | SWT.CTRL, ST.PASTE);
131 // styledText.setKeyBinding('X' | SWT.CTRL, ST.CUT);
132
133 styledText.setKeyBinding('A' | SWT.CTRL, ST.SELECT_ALL);
134
135 styledText.setKeyBinding('Z' | SWT.CTRL, ActionCode.UNDO);
136 styledText.setKeyBinding('Y' | SWT.CTRL, ActionCode.REDO);
137 styledText.setKeyBinding('F' | SWT.CTRL, ActionCode.CLEAR);
138 styledText.setKeyBinding('D' | SWT.CTRL, ActionCode.DELETE);
139
140 UndoManager undoManager = new UndoManager(50);
141 undoManager.connect(styledText);
142
143 styledText.setUndoManager(undoManager);
144
145 shell.open();
146
147 shell.layout();
148
149 while (! shell.isDisposed())
150 {
151 if (! display.readAndDispatch())
152 display.sleep();
153 }
154 }
155 }
本示例着重演示如何实现Undo/Redo操作,也可以参见JFace的TextViewer的Undo/Redo操作的实现。