阁主在工作过程中,用excel编制一些文件,需要记录变更履历。而excel没有一个方便的版本管理工具,阁主就想到了excel自带的vba环境。何不自己写一个出来呢?
先来说说编程思路,思路对了,程序就出来了一半。
首先,我们记录更改履历,就需要记录原值,而excel工作簿和工作表的change事件不能返回原值,所以阁主采用了一个取巧的办法,当然,在使用时候就有了限制。
软件思路
①更改发生,产生change事件,并伴随参数target和sh,target是变更的区域,sh是变更的工作表。
②worksheet的change事件代码运行。这时,可以获取到更改区域和更改后的值,将更改后的值存放到数组中以备用。
③调用application.undo方法。该方法放弃修改后的新值并返回原值。将原值存入数组。
④再次调用application.undo方法。恢复目标区域的值为修改后的值。
⑤将获取到的值写入变更履历工作表中
Option Explicit Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range) Dim TimeFormatString As String Dim TimeStamp As String Const VersionControlSheetName = "版本控制" '版本控制页名称有更改的,请变更此常量 TimeFormatString = "yyyy-mmm-dd HH:mm:ss" '时间戳文本格式 Dim AvailableZone As String AvailableZone = "A1:Q42" '有效区域地址 If Application.Union(Target, Range(AvailableZone)).Address = Range(AvailableZone).Address Then TimeStamp = Format(Now, TimeFormatString) ' ' Dim enableEventsFlag As Boolean enableEventsFlag = Application.EnableEvents Application.EnableEvents = False '防止循环调用 If Target.Areas.Count > 1 Then '多重区域无效 MsgBox ("只能选择单一区域进行更改") Application.Undo ' ElseIf Target.MergeCells Then ' MsgBox "不能用于合并单元格!" ' Application.Undo ElseIf Sh.Name <> VersionControlSheetName Then Dim newValue(), oldVaue(), rNum, cNum, rNums, cNums Dim actcel As Range Set actcel = ActiveCell rNums = Target.Rows.Count cNums = Target.Columns.Count '获取目标区域大小 ReDim newValue(1 To rNums, 1 To cNums) For rNum = 1 To rNums For cNum = 1 To cNums newValue(rNum, cNum) = Target.Cells(rNum, cNum).Formula Next Next '获取目标修改后区域值 Application.Undo ReDim oldvalue(1 To rNums, 1 To cNums) For rNum = 1 To rNums For cNum = 1 To cNums oldvalue(rNum, cNum) = Target.Cells(rNum, cNum).Formula Next Next '获取目标修改前区域值 Application.Undo Dim arr(), m m = 0 For rNum = 1 To rNums For cNum = 1 To cNums If (oldvalue(rNum, cNum) <> "" Or newValue(rNum, cNum) <> "") And (oldvalue(rNum, cNum) <> newValue(rNum, cNum)) Then m = m + 1 ReDim Preserve arr(1 To 5, 1 To m) arr(1, m) = TimeStamp '时间戳 arr(2, m) = "=hyperlink(""#" & Sh.Name & "!" & Target.Cells(rNum, cNum).Address & """,""" & Sh.Name & "!" & Target.Cells(rNum, cNum).Address & """)" '变更位置链接链接 arr(3, m) = oldvalue(rNum, cNum) '变更前值 arr(4, m) = newValue(rNum, cNum) '变更后值 End If Next Next Dim arr2 If m > 0 Then arr2 = Application.WorksheetFunction.Transpose(arr) Sheets(VersionControlSheetName).Range("A65535").End(xlUp).Resize(UBound(arr, 2), UBound(arr)).Offset(1, 0) = arr2 End If '处理并输出版本控制内容 actcel.Activate End If Application.EnableEvents = enableEventsFlag End If End Sub
更改履历能够处理的是使用者的键入、删除、更改操作,对于执行vba或者其他方面造成的更改无法记录。这是源于阁主使用了application.undo方法来获取更改位置的原值。而vba等操作过的数据,该方法不适用。