ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 11448|回复: 11

VB编程实现多步撤消 重做功能源码

[复制链接]

TA的精华主题

TA的得分主题

发表于 2013-8-4 17:41 | 显示全部楼层 |阅读模式
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖已被收录到知识树中,索引项:数据类型和基本语句
VB编程实现多步撤消 重做功能源码VB_multi_step_undo_redo_function_source.rar (8.73 KB, 下载次数: 464)

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-8-4 17:46 | 显示全部楼层

Undo/Redo的C#实现方式(原创)  

2007-09-17 23:28:56|  分类: 胡思乱想 |字号 订阅
     现代软件很多都配备了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;
        }
    }

TA的精华主题

TA的得分主题

发表于 2013-9-22 13:18 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
这个是个好东西啊,怎么会没有人顶起!

TA的精华主题

TA的得分主题

发表于 2013-9-29 14:52 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2013-9-29 17:00 | 显示全部楼层
jiangdong110 发表于 2013-9-22 13:18
这个是个好东西啊,怎么会没有人顶起!

是好东西,关键是不会用啊

TA的精华主题

TA的得分主题

发表于 2014-9-1 08:59 | 显示全部楼层
这玩意应用到excle vba或者vsto有实例么

TA的精华主题

TA的得分主题

发表于 2014-9-1 20:21 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
liucqa 发表于 2013-8-4 17:46
Undo/Redo的C#实现方式(原创)  

2007-09-17 23:28:56|  分类: 胡思乱想 |字号 订阅

关注中......

TA的精华主题

TA的得分主题

发表于 2014-9-1 22:41 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
2楼的代码,用VB/VBA也能实现

TA的精华主题

TA的得分主题

发表于 2014-9-2 06:57 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
如何在RichTextBox中实现Undo功能  作者:未知    来源:网络    更新时间:2013/3/1
一、一次撤销功能
二、无限地撤销功能

生活中的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哟!
(如果你用此代码于你编制的记事本,Bart Lorang要求给他发一个拷贝:BartLorang@POBoxes.com)

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

'申明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

欢迎转载,但请保留出处,本文章转自[华软源码],原文链接:http://www.hur.cn/Article/2013/120193.html

TA的精华主题

TA的得分主题

发表于 2014-9-2 08:06 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
win2009 发表于 2014-9-2 06:57
如何在RichTextBox中实现Undo功能  作者:未知    来源:网络    更新时间:2013/3/1
一、一次撤销功能
...

这个是在自己的控件上实现 我想知道的是 如何保留excle本身的undo功能
因为在vba运行后 excle的undo功能会撤销 vba的操作 会导致excle本身的undo功能丧失
有没有办法让vba运行后 保留住excle的undo功能 至少保留vba运行期对excle操作改变的undo能力(排除备份整表的方法)
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-17 20:30 , Processed in 0.042087 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表