给程序添加一剂“后悔药”
编程爱好者
用过Word的用户可能都知道使用“撤销”、“恢复”功能可以撤销我们刚刚进行的操作或恢复我们刚撤销的操作。这两项功能很多软件都提供。它们犹如一剂“后悔药”,使我们能避免因为误操作带来的损失。在程序设计中,我们经常使用富文本框(RichText Box)提供用户文本编辑功能。实际上,我们也可以为它添加一剂“后悔药”,以实现“撤销”、“恢复”功能。
1.编程思路
使用过富文本框控件的开发者都知道,每次用户在富文本框中进行修改时都会触发Change事件。这时,我们可以通过记录修改前的富文本框内容来为用户提供撤销功能。而恢复操作则是在用户进行撤销操作后才可以进行的,并且随着用户再次修改而变为不可用。我的方法就是使用两个堆栈分别记录可撤销的富文本框的内容和可恢复的富文本框内容。
(1)用户更改富文本框内容时,将修改前的内容压入撤销堆栈;

(2)用户执行撤销操作时,弹出撤销堆栈顶层内容,显示在富文本框,并压入恢复堆栈。

2.界面设计
为了演示富文本框的“撤销”、“恢复”功能,我们首先添加一个窗体,并在上面添加一个富文本框、两个按钮。富文本框给用户提供编辑文本的空间。两个按钮分别命名为cmdUndo与cmdRedo。这两个按钮为用户提供“撤销”与“恢复”功能(见图)。

3.代码设计
(1)公共变量声明
界面设计完成后,我们需要在窗体中声明几个公共变量:
Dim rtbUndoStack() As String '撤销堆栈
Dim rtbRedoStack() As String '恢复堆栈
Dim bChg As Boolean '记录富文本框内容是否发生变化
rtbUndoStack字符串数组存储富文本框过去执行的操作;rtbRedoStack存储用户执行撤销命令后可以恢复的操作。bChg变量则用于判断富文本框内容是否被修改。
(2)初始化变量
接下来,我们要为窗体设计代码,完成初始化工作:
Private Sub Form_Load()
ReDim Preserve rtbUndoStack(1) As String '改变数组元素个数
ReDim Preserve rtbRedoStack(1) As String
rtbUndoStack(1) = rtbText.TextRTF '初始化撤销堆栈元素
rtbRedoStack(1) = rtbText.TextRTF '初始化恢复堆栈元素
bChg = False
End Sub
初始化时分别将撤销及恢复堆栈定义为一个元素,并都存储为富文本框现在的内容。
(3)富文本框内容被修改时的代码
在富文本框内容每次发生变化时,我们要编写代码记录变化:
Private Sub rtbText_Change()
If bChg = False Then '判断富文本框内容是否首次被修改
ReDim Preserve rtbUndoStack(UBound(rtbUndoStack) + 1) As String '重新分配数组元素个数
rtbUndoStack(UBound(rtbUndoStack)) = rtbText.TextRTF '记录当前富文本框内容至恢复堆栈
ReDim Preserve rtbRedoStack(1) As String '清除恢复堆栈内容
Else
bChg = False
End If
'判断并设置撤销恢复按钮是否可用
If UBound(rtbUndoStack) > 1 Then
cmdUndo.Enabled = True
Else
cmdUndo.Enabled = False
End If
If UBound(rtbRedoStack) > 1 Then
cmdRedo.Enabled = True
Else
cmdRedo.Enabled = False
End If
End Sub
在记录变化时,我们除了要将富文本框的内容压入撤销堆栈,还要清除恢复堆栈内容。最后还要改变撤销及恢复按钮的可用状态。
(4)用户点击撤销按钮时的代码
用户点击撤销按钮后,我们要编写代码显示撤销堆栈栈顶内容,并将其压入恢复堆栈:
Private Sub cmdUndo_Click()
If UBound(rtbUndoStack) > 1 Then
bChg = True
ReDim Preserve rtbRedoStack(UBound(rtbRedoStack) + 1) As String '重新分配数组元素个数
rtbRedoStack(UBound(rtbRedoStack)) = rtbText.TextRTF '存储当前富文本框内容至恢复堆栈
rtbText.TextRTF = rtbUndoStack(UBound(rtbUndoStack) - 1) '显示撤销堆栈栈顶内容
ReDim Preserve rtbUndoStack(UBound(rtbUndoStack) - 1) As String '删除撤销堆栈栈顶内容
End If
End Sub
(5)恢复按钮代码
最后,我们还要为恢复按钮设计代码,来显示恢复的内容:
Private Sub cmdRedo_Click()
If UBound(rtbRedoStack) > 1 Then
bChg = True
ReDim Preserve rtbUndoStack(UBound(rtbUndoStack) + 1) As String '重新分配数组元素个数
rtbUndoStack(UBound(rtbUndoStack)) = rtbText.TextRTF '存储当前富文本框内容至撤销堆栈
rtbText.TextRTF = rtbRedoStack(UBound(rtbRedoStack)) '显示恢复堆栈栈顶内容
ReDim Preserve rtbRedoStack(UBound(rtbRedoStack) - 1) As String '删除恢复堆栈栈顶元素
End If
End Sub
至此代码设计部分就完成了。
4.代码剖析
上面的撤销、恢复功能使用两个数组实现撤销及恢复堆栈。用户每次在富文本框中进行修改时,修改前的内容就被压入撤销堆栈(rtbUndoStack)。用户进行撤销操作时,撤销堆栈的栈顶(也就是用户上一次的修改)被显示出来并压入恢复堆栈(rtbRedoStack)。当用户进行恢复操作时,恢复堆栈栈顶内容被显示出来并压入撤销堆栈。在两个堆栈中,为了保持富文本框的格式信息,实际上每个数组元素存储的都是RTF格式的字符串。
5.深入应用
上面的代码演示了撤销恢复功能的基本原理。如果开发者设计的是一个多文档界面,则上面的代码就会使各个文档之间的数据错乱。不过解决的方法很简单:就是在每个文档窗体设置上面提到的3个公共变量:
Dim rtbUndoStack() As String
Dim rtbRedoStack() As String
Dim bChg As Boolean
在使用时,我们可以通过ActiveForm.rtbUndoStack等访问或修改当前活动文档的撤销恢复堆栈。
在实际应用中,我们可能不仅在设计文本编辑程序时需要撤销恢复功能。在图片编辑,影片编辑时我们都需要。实际上,我们动一下脑筋,稍微修改一下上面的代码,便可在几乎任何场合使用。以图片编辑程序为例。图片框(Picture Box)控件也有Change事件,因此我们可以利用这个事件,在图片框的图片被改变时将改变前的图片存入堆栈。上面的代码只须改变两个堆栈的数据类型为stdPicture,如我们可以这样定义两个堆栈:
Dim picUndoStack() As StdPicture
Dim picRedoStack() As StdPicture
然后,在每次修改堆栈内容时将赋值语句内容再修改一下即可。在这里,我们可以将赋值语句改为:
Set picUndoStack(Index) = New StdPict
ure
Set picUndoStack(Index) = picEdit.Pict
ure
这样,图片编辑软件也有了撤销恢复功能。
其实,不管是什么编辑程序,只要控件拥有Change事件,我们只要修改数据类型及复制语句,上面的“后悔药”便可以在各种程序中通用。本程序特别适合在编辑器类程序中使用。
以上代码在Visual Basic 6.0中完成,Windows 2000/XP中调试通过。