龙空技术网

如何在RichTextBox中实现Undo功能 Excel VB编程实现多步撤消

Excel自力更生 136

前言:

今天兄弟们对“vbnettextbox数据提取”大体比较关切,看官们都需要知道一些“vbnettextbox数据提取”的相关资讯。那么小编在网络上收集了一些关于“vbnettextbox数据提取””的相关内容,希望小伙伴们能喜欢,小伙伴们快快来学习一下吧!

现代软件很多都配备了Undo/Redo功能,这个功能对于用户来说是十分方便,可以明显节约用户时间,增加软件的易用性,其中以Office和AutoCAD最为典型。本文以.NET环境中最常用的C#语言为例,详细分析了实现Undo/Redo功能的必要技术,并提供了可运行的C#代码清单。

实现Undo/Redo功能,就必须记录用户的操作和这些操作发生前受该操作影响的对象的值。当然不是每个操作都是值得关注的,比如“打开文件”,“最大化窗口”等这类不改变软件核心对象数据的操作,是完全不必记录下来的。

其中一些操作只改变一个对象的一个域的值,而一些操作可以改变很对个对象的多个域的值(例如AutoCAD中的分解命令,可以同时改变N个对象的状态)。为了简化问题,我们肯定希望不管是只改变一个对象的某个域的操作(被称为原子操作)还是一次改变多个对象的操作(被称为复合操作)都被以一种单一接口被提供 。这就要对操作进行包装,将原子操作打包成复合操作,或者说把复合操作伪装成原子操作,因为对于操作的调用者来说,这完全是透明的,他将感觉不到自己调用的是原子操作还是复合操作,也不必关心这个问题。

一个复合操作到底含有多少原子操作是事先无法确定的。ArrayList是.NET提供的一种可变的万能数组,它实际上是一个****对象,但是它的使用方式上看上去更像个可变的一维数组,很适合用于这种未知长度的情况。所以复合操作用ArrayList存放是合适的。

接下来的事情就是理解Undo/Redo的本质了。用户在Undo时,软件到底做了些什么呢?上面分析了所有有效操作都必须被记录下来,而且是被存放在数组或者是链表中,当用户Undo时,显然是要从数组或者链表中取回被保存的数据,然后赋值给相对应的对象。Redo发生时大概也是发生了这些事情,只是方向和Undo刚好是相反的。用户的操作不一定总是要插入数组/链表的末端。举个例子:当用户的先进行了10个操作,然后又Undo掉了5个操作,然后又进行了一个操作,这个操作是应该保存在数组/链表的第11个位置上还是应该保存在第6个位置上呢?答案显然是后者。只要又进行了一个操作,先前的被保存在Undo数组/链表中的第6到10个记录将全部变成无效。

那么用户在Redo时发生了什么呢?还是刚才的例子,用户Undo掉5个操作后又Redo了3次,显然应该和用户只进行了前8个操作是等价的。如果用户Undo掉5个操作而没有Redo,而是又进行了3个其他操作呢?这时候他再Redo会怎样?根据经验,结论是无法Redo了,Redo操作必须紧随Undo才行。因为Undo数组中第9,10两个操作早已经是无效了,再Redo它们,是绝对错误的事情。

至此,关于Undo/Redo的分析就完成了,接下来就是编码。下面提供的是在VS2005上编译通过的C#源代码:

public class CUndo

{

//private ArrayList actionList;//复合操作队列,每个actionList的子成员为CAction对象

private CActionNode[] actionList;

private int iMaxUndoTime;//操作队列的最大长度

private bool bCanUndo=false;//当前状态下是否可以进行Undo操作

public bool CanUndo

{

get { return bCanUndo; }

}

private bool bCanRedo = false;//当前状态下是否可以进行Redo操作

public bool CanRedo

{

get { return bCanRedo; }

}

public bool bStartState = false;//指示是否是第一次记录操作

private int iActionListLength= 0;//有效操作队列的长度,它并不一定等于actionList.count

private int iCurrentActionPointer= -1;//指向actionList当前操作的指针,新添加的操作从该

//位置的下一位开始,Undo操作从它的上一个位置开始,Redo操作从它下一位开始

public CUndo(int maxUndoTime)

{

actionList = new CActionNode[maxUndoTime];//为actionList分配内存空间

iMaxUndoTime = maxUndoTime;//设置可Undo的最大次数

}

public int ActionListLength//获取当前操作队列的实际长度

{

get { return iActionListLength;}

}

public int CurrentActionPointer//获取当前Undo指针的位置

{

get {return iCurrentActionPointer;}

}

//向操作队列中加入操作,插入成功就返回true,否则为false

public bool addActionIntoActionList(CActionNode act)

{

//如果不超过Undo队列的最大允许长度就可以插入

if (iCurrentActionPointer< iMaxUndoTime-1)

{

actionList[++iCurrentActionPointer]= act;

iActionListLength = iCurrentActionPointer;

bCanUndo = true;//只要有操作进入队列,bCanUndo就为真

return true;

}

else

return false;

}

public bool undo(int iUndoTime)

{

//如果undo次数大于iCurrentActionPointer指示的长度,就报错

//因为Undo队列的最大长度就是iCurrentActionPointer+1

if (iUndoTime > iCurrentActionPointer+1)

return false;

CActionNode act;

while (iUndoTime > 0 && iCurrentActionPointer>0)

{

//从Undo队列中取回操作

act = actionList[--iCurrentActionPointer];

iUndoTime--;

//如果取回操作失败,返回false

if (act.getActionsInActionNode() == false)

return false;

}

bCanRedo = true;//如果成功Undo,就置bCanRedo为真

return true;

}

public bool redo(int iRedoTime)

{

if (iRedoTime > iActionListLength - iCurrentActionPointer)

return false;

CActionNode act;

while (iRedoTime>0 )

{

act = actionList[++iCurrentActionPointer];

iRedoTime--;

Debug.Assert(act != null, iCurrentActionPointer.ToString());

if (act.getActionsInActionNode()==false)

return false;

}

bCanUndo = true;//如果成功Redo,就置bCanUndo为真

return true;

}

}

如何在RichTextBox中实现Undo功能

一、一次撤销功能

二、无限地撤销功能

生活中的What's done cannot be undone在我们的程序中应该改为What's done can always be undone。你不相信?那么请看——

如果仅仅象MS的小记事本那样只有一次undo功能,那不是一件麻烦事,用SendMessage函数就可以轻松实现。下列代码能使RichTextBox有一次撤销操作的功能:

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Const WM_UNDO = &H304

'下一行为按钮或菜单代码

SendMessage RichTextBox1.hwnd, WM_UNDO, 0, 0

是不是很容易?不过,想要无限地undo下次,就不那么简单了。土人曾拟编写一个,却无意中发现了Bart Lorang,一个年仅十多岁的美国小子已经在网上公开了类似的代码。这家伙敢跟老盖叫劲儿,号称"Not the next Bill Gates, but the first Bart Lorang",好大的口气!不过他的程序确实不错,现特意将其内容拿出来给大家瞧瞧。为了适用于中文环境,土人对源码作了些微改动。注意:不仅可以undo,还可以redo哟!

' ****** 模块代码:

'申明API函数

Public Declare Function SendMessage Lib "User32" Alias "SendMessageA" _

(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _

lParam As Long) As Long

'常数

Public Const WM_USER = &H400

Public Const EM_HIDESELECTION = WM_USER + 63

' ****** 类模块代码:

Public SelStart As Long '文本框中的开始位置

Public TextLen As Long '文本长度

Public Text As String '文本内容

' ****** 窗体代码:

'请给窗体添加按钮两个、RichTextBox一个,取默认值;

'菜单若干:——

'层次 Name属性 Caption属性

' 1 Edit 编辑

' 2 mnuUndo 撤销

' 2 mnuRedo 恢复

' 2 mnuCut 剪切

' 2 mnuCopy 复制

' 2 mnuPaste 粘贴

' 2 mnuDelete 删除

' 2 mnuSelectAll 全选

Private trapUndo As Boolean

Private UndoStack As New Collection '可撤销的集合

Private RedoStack As New Collection '可恢复的集合

Private Sub Command2_Click()

Redo

End Sub

Private Sub Command1_Click()

Undo

End Sub

Private Sub Form_Load()

RichTextBox1.Text = ""

Command1.Caption = "撤销"

Command2.Caption = "恢复"

trapUndo = True

RichTextBox1_Change

RichTextBox1_SelChange

Show

DoEvents

End Sub

Private Sub mnuCopy_Click()

Clipboard.SetText RichTextBox1.SelText, 1 '拷贝

End Sub

Private Sub mnuCut_Click()

Clipboard.SetText RichTextBox1.SelText, 1 '剪切

RichTextBox1.SelText = ""

End Sub

Private Sub mnuDelete_Click()

RichTextBox1.SelText = "" '删除

End Sub

Private Sub mnuPaste_Click()

RichTextBox1.SelText = "" '这一步对Undo功能至关重要

RichTextBox1.SelText = Clipboard.GetText(1) '粘贴

End Sub

Private Sub mnuRedo_Click()

Command2_Click

End Sub

Private Sub mnuSelectAll_Click()

'全选

RichTextBox1.SelStart = 0

RichTextBox1.SelLength = Len(RichTextBox1.Text)

End Sub

Private Sub mnuUndo_Click()

Command1_Click

End Sub

Private Sub RichTextBox1_Change()

If Not trapUndo Then Exit Sub '因为because trapping is disabled

Dim newElement As New UndoElement '创建新的undo集合

Dim c%, l&

'移除所有的Redo项目

For c% = 1 To RedoStack.Count

RedoStack.Remove 1

Next c%

'给新集合赋值

newElement.SelStart = RichTextBox1.SelStart

newElement.TextLen = Len(RichTextBox1.Text)

newElement.Text = RichTextBox1.Text

'将其加入 undo 堆栈

UndoStack.Add Item:=newElement

'设置窗体控件的属性

EnableControls

End Sub

Private Sub RichTextBox1_KeyDown(KeyCode As Integer, Shift As Integer)

If Shift = 2 Then

KeyCode = 0

End If

End Sub

Private Sub RichTextBox1_KeyUp(KeyCode As Integer, Shift As Integer)

If KeyCode = vbKeySpace Then

RichTextBox1.SelFontName = "宋体" '定义字体

End If

End Sub

Private Sub RichTextBox1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)

If Button = vbRightButton Then '显示

PopupMenu mnuEdit

End If

End Sub

'菜单属性设置

Private Sub RichTextBox1_SelChange()

Dim ln&

If Not trapUndo Then Exit Sub

ln& = RichTextBox1.SelLength

mnuCut.Enabled = ln& '不选择文本则禁用

mnuCopy.Enabled = ln& '同上

mnuPaste.Enabled = Len(Clipboard.GetText(1)) '剪贴版为空则禁用

mnuDelete.Enabled = ln& '不选择文本则禁用

mnuSelectAll.Enabled = CBool(Len(RichTextBox1.Text)) '文本框无内容则禁用

End Sub

'设置按钮、菜单属性

Private Sub EnableControls()

Command1.Enabled = UndoStack.Count > 1

Command2.Enabled = RedoStack.Count > 0

mnuUndo.Enabled = Command1.Enabled

mnuRedo.Enabled = Command2.Enabled

RichTextBox1_SelChange

End Sub

'Change子程序

Public Function Change(ByVal lParam1 As String, ByVal lParam2 As String, startSearch As Long) As String

Dim tempParam$

Dim d&

If Len(lParam1) > Len(lParam2) Then '交换

tempParam$ = lParam1

lParam1 = lParam2

lParam2 = tempParam$

End If

d& = Len(lParam2) - Len(lParam1)

Change = Mid(lParam2, startSearch - d&, d&)

End Function

'Undo子程序

Public Sub Undo()

Dim chg$, X&

Dim DeleteFlag As Boolean '标志删除或添加变量

Dim objElement As Object, objElement2 As Object

If UndoStack.Count > 1 And trapUndo Then

trapUndo = False

DeleteFlag = UndoStack(UndoStack.Count - 1).TextLen < UndoStack(UndoStack.Count).TextLen

If DeleteFlag Then '删除

'cmdDummy.SetFocus '改变焦点

X& = SendMessage(RichTextBox1.hWnd, EM_HIDESELECTION, 1&, 1&)

Set objElement = UndoStack(UndoStack.Count)

Set objElement2 = UndoStack(UndoStack.Count - 1)

RichTextBox1.SelStart = objElement.SelStart - (objElement.TextLen - objElement2.TextLen)

RichTextBox1.SelLength = objElement.TextLen - objElement2.TextLen

RichTextBox1.SelText = ""

X& = SendMessage(RichTextBox1.hWnd, EM_HIDESELECTION, 0&, 0&)

Else '添加

Set objElement = UndoStack(UndoStack.Count - 1)

Set objElement2 = UndoStack(UndoStack.Count)

chg$ = Change(objElement.Text, objElement2.Text, _

objElement2.SelStart + 1 + Abs(Len(objElement.Text) - Len(objElement2.Text)))

RichTextBox1.SelStart = objElement2.SelStart

RichTextBox1.SelLength = 0

RichTextBox1.SelText = chg$

RichTextBox1.SelStart = objElement2.SelStart

If Len(chg$) > 1 And chg$ <> vbCrLf Then

RichTextBox1.SelLength = Len(chg$)

Else

RichTextBox1.SelStart = RichTextBox1.SelStart + Len(chg$)

End If

End If

RedoStack.Add Item:=UndoStack(UndoStack.Count)

UndoStack.Remove UndoStack.Count

End If

EnableControls

trapUndo = True

RichTextBox1.SetFocus

End Sub

'Redo子程序

Public Sub Redo()

Dim chg$

Dim DeleteFlag As Boolean '标志删除或添加文本的变量

Dim objElement As Object

If RedoStack.Count > 0 And trapUndo Then

trapUndo = False

DeleteFlag = RedoStack(RedoStack.Count).TextLen < Len(RichTextBox1.Text)

If DeleteFlag Then '为真则删除

Set objElement = RedoStack(RedoStack.Count)

RichTextBox1.SelStart = objElement.SelStart

RichTextBox1.SelLength = Len(RichTextBox1.Text) - objElement.TextLen

RichTextBox1.SelText = ""

Else '反之则添加

Set objElement = RedoStack(RedoStack.Count)

chg$ = Change(RichTextBox1.Text, objElement.Text, objElement.SelStart + 1)

RichTextBox1.SelStart = objElement.SelStart - Len(chg$)

RichTextBox1.SelLength = 0

RichTextBox1.SelText = chg$

RichTextBox1.SelStart = objElement.SelStart - Len(chg$)

If Len(chg$) > 1 And chg$ <> vbCrLf Then

RichTextBox1.SelLength = Len(chg$)

Else

RichTextBox1.SelStart = RichTextBox1.SelStart + Len(chg$)

End If

End If

UndoStack.Add Item:=objElement

RedoStack.Remove RedoStack.Count

End If

EnableControls

trapUndo = True

RichTextBox1.SetFocus

End Sub

标签: #vbnettextbox数据提取