阁主在工作过程中,用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等操作过的数据,该方法不适用。