简体   繁体   English

如何在 Excel VBA 中获取已更改单元格的旧值?

[英]How do I get the old value of a changed cell in Excel VBA?

I'm detecting changes in the values of certain cells in an Excel spreadsheet like this...我正在检测这样的 Excel 电子表格中某些单元格值的变化......

Private Sub Worksheet_Change(ByVal Target As Range)
Dim cell As Range
Dim old_value As String
Dim new_value As String

For Each cell In Target

    If Not (Intersect(cell, Range("cell_of_interest")) Is Nothing) Then
        new_value = cell.Value
        old_value = ' what here?
        Call DoFoo (old_value, new_value)
    End If

Next cell

End Sub

Assuming this isn't too bad a way of coding this, how do I get the value of the cell before the change?假设这不是太糟糕的编码方式,我如何在更改之前获取单元格的值?

try this试试这个

declare a variable say声明一个变量说

Dim oval

and in the SelectionChange Event并在SelectionChange事件中

Public Sub Worksheet_SelectionChange(ByVal Target As Range)
oval = Target.Value
End Sub

and in your Worksheet_Change event set并在您的Worksheet_Change事件集中

old_value = oval

You can use an event on the cell change to fire a macro that does the following:您可以在单元格更改上使用事件来触发执行以下操作的宏:

vNew = Range("cellChanged").value
Application.EnableEvents = False
Application.Undo
vOld = Range("cellChanged").value
Range("cellChanged").value = vNew
Application.EnableEvents = True 

I had to do it too.我也不得不这样做。 I found the solution from "Chris R" really good, but thought it could be more compatible in not adding any references.我发现“Chris R”的解决方案非常好,但认为不添加任何引用可能更兼容。 Chris, you talked about using Collection.克里斯,你谈到了使用 Collection。 So here is another solution using Collection.所以这是另一个使用 Collection 的解决方案。 And it's not that slow, in my case.就我而言,它并没有那么慢。 Also, with this solution, in adding the event "_SelectionChange", it's always working (no need of workbook_open).此外,使用此解决方案,在添加事件“_SelectionChange”时,它始终有效(不需要 workbook_open)。

Dim OldValues As New Collection

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    'Copy old values
    Set OldValues = Nothing
    Dim c As Range
    For Each c In Target
        OldValues.Add c.Value, c.Address
    Next c
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)
    On Local Error Resume Next  ' To avoid error if the old value of the cell address you're looking for has not been copied
    Dim c As Range
    For Each c In Target
        Debug.Print "New value of " & c.Address & " is " & c.Value & "; old value was " & OldValues(c.Address)
    Next c
    'Copy old values (in case you made any changes in previous lines of code)
    Set OldValues = Nothing
    For Each c In Target
        OldValues.Add c.Value, c.Address
    Next c
End Sub

I have an alternative solution for you.我有一个替代解决方案。 You could create a hidden worksheet to maintain the old values for your range of interest.您可以创建一个隐藏的工作表来维护您感兴趣的范围的旧值。

Private Sub Workbook_Open()

Dim hiddenSheet As Worksheet

Set hiddenSheet = Me.Worksheets.Add
hiddenSheet.Visible = xlSheetVeryHidden
hiddenSheet.Name = "HiddenSheet"

'Change Sheet1 to whatever sheet you're working with
Sheet1.UsedRange.Copy ThisWorkbook.Worksheets("HiddenSheet").Range(Sheet1.UsedRange.Address)

End Sub

Delete it when the workbook is closed...工作簿关闭时删除它...

Private Sub Workbook_BeforeClose(Cancel As Boolean)

Application.DisplayAlerts = False
Me.Worksheets("HiddenSheet").Delete
Application.DisplayAlerts = True

End Sub

And modify your Worksheet_Change event like so...并像这样修改您的 Worksheet_Change 事件...

For Each cell In Target

    If Not (Intersect(cell, Range("cell_of_interest")) Is Nothing) Then
        new_value = cell.Value
        ' here's your "old" value...
        old_value = ThisWorkbook.Worksheets("HiddenSheet").Range(cell.Address).Value
        Call DoFoo(old_value, new_value)
    End If

Next cell

' Update your "old" values...
ThisWorkbook.Worksheets("HiddenSheet").UsedRange.Clear
Me.UsedRange.Copy ThisWorkbook.Worksheets("HiddenSheet").Range(Me.UsedRange.Address)

Here's a way I've used in the past.这是我过去使用的一种方法。 Please note that you have to add a reference to the Microsoft Scripting Runtime so you can use the Dictionary object - if you don't want to add that reference you can do this with Collections but they're slower and there's no elegant way to check .Exists (you have to trap the error).请注意,您必须添加对 Microsoft Scripting Runtime 的引用,以便您可以使用 Dictionary 对象 - 如果您不想添加该引用,您可以使用 Collections 执行此操作,但它们速度较慢,并且没有优雅的检查方法.Exists(您必须捕获错误)。

Dim OldVals As New Dictionary
Private Sub Worksheet_Change(ByVal Target As Range)
Dim cell As Range
    For Each cell In Target
        If OldVals.Exists(cell.Address) Then
            Debug.Print "New value of " & cell.Address & " is " & cell.Value & "; old value was " & OldVals(cell.Address)
        Else
            Debug.Print "No old value for " + cell.Address
        End If
        OldVals(cell.Address) = cell.Value
    Next
End Sub

Like any similar method, this has its problems - first off, it won't know the "old" value until the value has actually been changed.像任何类似的方法一样,这有其问题 - 首先,在实际更改值之前,它不会知道“旧”值。 To fix this you'd need to trap the Open event on the workbook and go through Sheet.UsedRange populating OldVals.要解决此问题,您需要在工作簿上捕获 Open 事件并通过 Sheet.UsedRange 填充 OldVals。 Also, it will lose all its data if you reset the VBA project by stopping the debugger or some such.此外,如果您通过停止调试器或类似方式重置 VBA 项目,它将丢失所有数据。

an idea ...一个想法……

  • write these in the ThisWorkbook moduleThisWorkbook模块中写这些
  • close and open the workbook关闭和打开工作簿
Public LastCell As Range

    Private Sub Workbook_Open()

        Set LastCell = ActiveCell

    End Sub

    Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)

        Set oa = LastCell.Comment

        If Not oa Is Nothing Then
        LastCell.Comment.Delete
        End If

        Target.AddComment Target.Address
        Target.Comment.Visible = True
        Set LastCell = ActiveCell

    End Sub

Place the following in the CODE MODULE of a WORKSHEET to track the last value for every cell in the used range:将在工作表代码模块以下跟踪的最后一个值在使用范围内的每一个细胞

Option Explicit

Private r As Range
Private Const d = "||"

Public Function ValueLast(r As Range)
    On Error Resume Next
    ValueLast = Split(r.ID, d)(1)
End Function

Private Sub Worksheet_Activate()
    For Each r In Me.UsedRange: Record r: Next
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)
    For Each r In Target: Record r: Next
End Sub

Private Sub Record(r)
    r.ID = r.Value & d & Split(r.ID, d)(0)
End Sub

And that's it.就是这样。

This solution uses the obscure and almost never used Range.ID property, which allows the old values to persist when the workbook is saved and closed.此解决方案使用了晦涩难懂且几乎从未使用过的 Range.ID 属性,它允许在保存和关闭工作簿时保留旧值。

At any time you can get at the old value of a cell and it will indeed be different than a new current value:您可以随时获取单元格的旧值,它确实与新的当前值不同:

With Sheet1
    MsgBox .[a1].Value
    MsgBox .ValueLast(.[a1])
End With

try this, it will not work for the first selection, then it will work nice :)试试这个,它不适用于第一个选择,然后它会很好用 :)

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    On Error GoTo 10
    If Target.Count > 1 Then GoTo 10
    Target.Value = lastcel(Target.Value)
    10
End Sub


Function lastcel(lC_vAl As String) As String
    Static vlu
    lastcel = vlu
    vlu = lC_vAl
End Function

I had a need to capture and compare old values to the new values entered into a complex scheduling spreadsheet.我需要捕获旧值并将其与输入到复杂调度电子表格中的新值进行比较。 I needed a general solution which worked even when the user changed many rows at the same time.我需要一个通用的解决方案,即使用户同时更改多行也能正常工作。 The solution implemented a CLASS and a COLLECTION of that class.该解决方案实现了该类的 CLASS 和 COLLECTION。

The class: oldValue类:oldValue

Private pVal As Variant
Private pAdr As String
Public Property Get Adr() As String
   Adr = pAdr
End Property
Public Property Let Adr(Value As String)
    pAdr = Value
End Property
Public Property Get Val() As Variant
   Val = pVal
End Property
Public Property Let Val(Value As Variant)
   pVal = Value
End Property

There are three sheets in which i track cells.我在三张纸中跟踪单元格。 Each sheet gets its own collection as a global variable in the module named ProjectPlan as follows:每个工作表都有自己的集合作为名为 ProjectPlan 的模块中的全局变量,如下所示:

Public prepColl As Collection
Public preColl As Collection
Public postColl As Collection
Public migrColl As Collection

The InitDictionaries SUB is called out of worksheet.open to establish the collections.从 worksheet.open 中调用 InitDictionaries SUB 以建立集合。

Sub InitDictionaries()
    Set prepColl = New Collection
    Set preColl = New Collection
    Set postColl = New Collection
    Set migrColl = New Collection
End Sub

There are three modules used to manage each collection of oldValue objects they are Add, Exists, and Value.有三个模块用于管理每个 oldValue 对象集合,它们是 Add、Exists 和 Value。

Public Sub Add(ByRef rColl As Collection, ByVal sAdr As String, ByVal sVal As Variant)
    Dim oval As oldValue
    Set oval = New oldValue
    oval.Adr = sAdr
    oval.Val = sVal
    rColl.Add oval, sAdr
End Sub

Public Function Exists(ByRef rColl As Collection, ByVal sAdr As String) As Boolean
   Dim oReq As oldValue
   On Error Resume Next
   Set oReq = rColl(sAdr)
   On Error GoTo 0

   If oReq Is Nothing Then
      Exists = False
   Else
      Exists = True
   End If
End Function
Public Function Value(ByRef rColl As Collection, ByVal sAdr) As Variant
   Dim oReq As oldValue
   If Exists(rColl, sAdr) Then
      Set oReq = rColl(sAdr)
      Value = oReq.Val
   Else
      Value = ""
   End If
End Function

The heavy lifting is done in the Worksheet_SelectionChange callback.繁重的工作在 Worksheet_SelectionChange 回调中完成。 One of the four is shown below.四个之一如下所示。 The only difference is the collection used in the ADD and EXIST calls.唯一的区别是 ADD 和 EXIST 调用中使用的集合。

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
   Dim mode As Range
   Set mode = Worksheets("schedule").Range("PlanExecFlag")
   If mode.Value = 2 Then
      Dim c As Range
      For Each c In Target
          If Not ProjectPlan.Exists(prepColl, c.Address) Then
             Call ProjectPlan.Add(prepColl, c.Address, c.Value)
          End If
      Next c
   End If
End Sub

THe VALUE call is called out of code executed from the Worksheet_Change Callback for example.例如,VALUE 调用是从 Worksheet_Change Callback 执行的代码中调用的。 I need to assign the correct collection based on the sheet name:我需要根据工作表名称分配正确的集合:

   Dim rColl As Collection
   If sheetName = "Preparations" Then
       Set rColl = prepColl
   ElseIf sheetName = "Pre-Tasks" Then
       Set rColl = preColl
   ElseIf sheetName = "Migr-Tasks" Then
       Set rColl = migrColl
   ElseIf sheetName = "post-Tasks" Then
       Set rColl = postColl
   Else
   End If

and then i am free to compute compare the some current value to the original value.然后我可以自由计算将某个当前值与原始值进行比较。

If Exists(rColl, Cell.Offset(0, 0).Address) Then
   tsk_delay = Cell.Offset(0, 0).Value - Value(rColl, Cell.Offset(0, 0).Address)
Else
   tsk_delay = 0
End If

Mark马克

Let's first see how to detect and save the value of a single cell of interest.我们先来看看如何检测和保存感兴趣的单个单元格的值。 Suppose Worksheets(1).Range("B1") is your cell of interest.假设Worksheets(1).Range("B1")是您感兴趣的单元格。 In a normal module, use this:在普通模块中,使用这个:

Option Explicit

Public StorageArray(0 to 1) As Variant 
    ' Declare a module-level variable, which will not lose its scope as 
      ' long as the codes are running, thus performing as a storage place.
    ' This is a one-dimensional array. 
      ' The first element stores the "old value", and 
      ' the second element stores the "new value"

Sub SaveToStorageArray()
' ACTION
    StorageArray(0) = StorageArray(1)
        ' Transfer the previous new value to the "old value"

    StorageArray(1) = Worksheets(1).Range("B1").value 
        ' Store the latest new value in Range("B1") to the "new value"

' OUTPUT DEMONSTRATION (Optional)
    ' Results are presented in the Immediate Window.
    Debug.Print "Old value:" & vbTab & StorageArray(0)
    Debug.Print "New value:" & vbTab & StorageArray(1) & vbCrLf

End Sub

Then in the module of Worksheets(1):然后在 Worksheets(1) 模块中:

Option Explicit

Private HasBeenActivatedBefore as Boolean
    ' Boolean variables have the default value of False.
    ' This is a module-level variable, which will not lose its scope as 
      ' long as the codes are running.

Private Sub Worksheet_Activate()        
    If HasBeenActivatedBefore = False then
        ' If the Worksheet has not been activated before, initialize the
          ' StorageArray as follows.

        StorageArray(1) = Me.Range("B1")
            ' When the Worksheets(1) is activated, store the current value
              ' of Range("B1") to the "new value", before the 
              ' Worksheet_Change event occurs.

        HasBeenActivatedBefore = True
            ' Set this parameter to True, so that the contents
              ' of this if block won't be evaluated again. Therefore, 
              ' the initialization process above will only be executed 
              ' once.
    End If
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)
    If Not Intersect(Target, Me.Range("B1")) Is Nothing then
        Call SaveToStorageArray
            ' Only perform the transfer of old and new values when 
              ' the cell of interest is being changed.
    End If
End Sub

This will capture the change of the Worksheets(1).Range("B1") , whether the change is due to the user actively selecting that cell on the Worksheet and changing the value, or due to other VBA codes that change the value of Worksheets(1).Range("B1") .这将捕获Worksheets(1).Range("B1")的更改,无论更改是由于用户主动选择 Worksheet 上的该单元格并更改值,还是由于其他 VBA 代码更改了值Worksheets(1).Range("B1")

Since we have declared the variable StorageArray as public, you can reference its latest value in other modules in the same VBA project.由于我们已将变量StorageArray声明为 public,因此您可以在同一 VBA 项目的其他模块中引用其最新值。

To expand our scope to the detection and saving the values of multiple cells of interest, you need to:要将我们的范围扩展到检测和保存多个感兴趣单元格的值,您需要:

  • Declare the StorageArray as a two-dimensional array, with the number of rows equal to the number of cells you are monitoring.StorageArray声明为二维数组,行数等于您正在监视的单元格数。
  • Modify the Sub SaveToStorageArray procedure to a more general Sub SaveToStorageArray(TargetSingleCell as Range) and change the relevant codes.Sub SaveToStorageArray过程修改为更通用的Sub SaveToStorageArray(TargetSingleCell as Range)并更改相关代码。
  • Modify the Private Sub Worksheet_Change procedure to accommodate the monitoring of those multiple cells.修改Private Sub Worksheet_Change过程以适应对这些多个单元格的监视。

Appendix: For more information on the lifetime of variables, please refer to: https://msdn.microsoft.com/en-us/library/office/gg278427.aspx附录:关于变量生命周期的更多信息,请参考: https : //msdn.microsoft.com/en-us/library/office/gg278427.aspx

In response to Matt Roy answer, I found this option a great response, although I couldn't post as such with my current rating.在回应 Matt Roy 的回答时,我发现这个选项是一个很好的回应,尽管我无法用我目前的评分发布这样的帖子。 :( :(

However, while taking the opportunity to post my thoughts on his response, I thought I would take the opportunity to include a small modification.然而,在借此机会发表我对他的回应的想法时,我想我会借此机会进行一些小的修改。 Just compare code to see.对比一下代码就知道了。

So thanks to Matt Roy for bringing this code to our attention, and Chris.R for posting original code.所以感谢 Matt Roy 让我们注意到这段代码,感谢 Chris.R 发布原始代码。

Dim OldValues As New Collection

Private Sub Worksheet_SelectionChange(ByVal Target As Range)

'>> Prevent user from multiple selection before any changes:

 If Selection.Cells.Count > 1 Then
        MsgBox "Sorry, multiple selections are not allowed.", vbCritical
        ActiveCell.Select
        Exit Sub
    End If
 'Copy old values
    Set OldValues = Nothing
    Dim c As Range
    For Each c In Target
        OldValues.Add c.Value, c.Address
    Next c
End Sub


Private Sub Worksheet_Change(ByVal Target As Range)
On Error Resume Next

 On Local Error Resume Next  ' To avoid error if the old value of the cell address you're looking for has not been copied

Dim c As Range

    For Each c In Target
        If OldValues(c.Address) <> "" And c.Value <> "" Then 'both Oldvalue and NewValue are Not Empty
                    Debug.Print "New value of " & c.Address & " is " & c.Value & "; old value was " & OldValues(c.Address)
        ElseIf OldValues(c.Address) = "" And c.Value = "" Then 'both Oldvalue and NewValue are  Empty
                    Debug.Print "New value of " & c.Address & " is Empty " & c.Value & "; old value is Empty" & OldValues(c.Address)

        ElseIf OldValues(c.Address) <> "" And c.Value = "" Then 'Oldvalue is Empty and NewValue is Not Empty
                    Debug.Print "New value of " & c.Address & " is Empty" & c.Value & "; old value was " & OldValues(c.Address)
        ElseIf OldValues(c.Address) = "" And c.Value <> "" Then 'Oldvalue is Not Empty and NewValue is Empty
                    Debug.Print "New value of " & c.Address & " is " & c.Value & "; old value is Empty" & OldValues(c.Address)
        End If
    Next c

    'Copy old values (in case you made any changes in previous lines of code)
    Set OldValues = Nothing
    For Each c In Target
        OldValues.Add c.Value, c.Address
    Next c

I needed this feature and I did not like all the solutions above after trying most as they are either我需要这个功能,但我不喜欢上面的所有解决方案,因为它们也是

  1. Slow
  2. Have complex implications like using application.undo.具有复杂的含义,例如使用 application.undo。
  3. Do not capture if they were not selected如果未选中,则不捕获
  4. Do not captures values if they were not changed before如果之前未更改,则不捕获值
  5. Too complex太复杂

Well I thought very hard about it and I completed a solution for a full UNDO,REDO history.好吧,我仔细考虑了它,并完成了一个完整的 UNDO,REDO 历史的解决方案。

To capture the old value it is actually very easy and very fast.要捕获旧值,它实际上非常容易且非常快速。

My solution is to capture all values once the user open the sheet is open into a variable and it gets updated after each change.我的解决方案是在用户打开工作表后捕获所有值,并在每次更改后更新。 this variable will be used to check the old value of the cell.此变量将用于检查单元格的旧值。 In the solutions above all of them used for loop.在上面的所有解决方案中,它们都用于循环。 Actually there is way easier method.其实还有更简单的方法。

To capture all the values I used this simple command为了捕获我使用这个简单命令的所有值

SheetStore = sh.UsedRange.Formula

Yeah, just that, Actually excel will return an array if the range is a multiple cells so we do not need to use FOR EACH command and it is very fast是的,就是这样,如果范围是多个单元格,实际上 excel 将返回一个数组,因此我们不需要使用 FOR EACH 命令,而且速度非常快

The following sub is the full code which should be called in Workbook_SheetActivate.以下 sub 是应在 Workbook_SheetActivate 中调用的完整代码。 Another sub should be created to capture the changes.应该创建另一个子来捕获更改。 Like, I have a sub called "catchChanges" that runs on Workbook_SheetChange.就像,我有一个名为“catchChanges”的子程序,它在 Workbook_SheetChange 上运行。 It will capture the changes then save them on another a change history sheet.它将捕获更改,然后将它们保存在另一个更改历史记录表中。 then runs UpdateCache to update the cache with the new values然后运行 ​​UpdateCache 以使用新值更新缓存

' should be added at the top of the module
Private SheetStore() As Variant 
Private SheetStoreName As String  ' I use this variable to make sure that the changes I captures are in the same active sheet to prevent overwrite

Sub UpdateCache(sh As Object)
      If sh.Name = ActiveSheet.Name Then ' update values only if the changed values are in the activesheet
          SheetStoreName = sh.Name
          ReDim SheetStore(1 To sh.UsedRange.Rows.count, 1 To sh.UsedRange.Columns.count) ' update the dimension of the array to match used range
          SheetStore = sh.UsedRange.Formula
      End If
End Sub

now to get the old value it is very easy as the array have the same address of cells现在要获得旧值很容易,因为数组具有相同的单元格地址

examples if we want cell D12 we can use the following例如,如果我们想要单元格 D12,我们可以使用以下内容

SheetStore(row_number,column_number)
'example
return = SheetStore(12,4)
' or the following showing how I used it. 
set cell = activecell ' the cell that we want to find the old value for
newValue = cell.value ' you can ignore this line, it is just a demonstration
oldValue = SheetStore(cell.Row, cell.Column)

these are snippet explaining the method, I hope everyone like it这些是解释方法的片段,希望大家喜欢

I have the same problem like you and luckily I have read the solution from this link: http://access-excel.tips/value-before-worksheet-change/我有和你一样的问题,幸运的是我已经从这个链接中阅读了解决方案: http : //access-excel.tips/value-before-worksheet-change/

Dim oldValue As Variant
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    oldValue = Target.Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
    'do something with oldValue...
End Sub

Note: you must place oldValue variable as a global variable so all subclasses can use it.注意:您必须将oldValue变量作为全局变量放置,以便所有子类都可以使用它。

I've expanded a bit on Matt Roy's solution which is awesome by the way.我对 Matt Roy 的解决方案进行了一些扩展,顺便说一下,这很棒。 What I did is handle situations when the user selects the whole row/column, so the macro only record the intersection between selection and ".UsedRange", and also handled situations where selection is not a range (for buttons, shapes, pivot tables)我所做的是处理用户选择整行/列的情况,因此宏只记录选择和“.UsedRange”之间的交集,还处理选择不是范围的情况(对于按钮、形状、数据透视表)

Sub trackChanges_loadOldValues_toCollection(ByVal Target As Range)
    'LOADS SELECTION AND VALUES INTO THE COLLECTION collOldValues
    If isErrorHandlingOff = False Then: On Error GoTo endWithError
    
    Dim RngI As Range, newTarget As Range, arrValues, arrFormulas, arrAddress
    
    'DON'T RECORD WHEN SELECTING BUTTONS OR SHAPES, ONLY FOR RANGES
    If TypeName(Target) <> "Range" Then: Exit Sub
    
    'RESET OLD VALUES COLLECITON
    Set collOldValues = Nothing
    
    'ONLY RECORD CELLS IN USED RANGE, TO AVOID ISSUES WHEN SELECTING WHOLE ROW
    Set newTarget = Intersect(Target, Target.Parent.UsedRange)
    
    'newTarget.Select
    If Not newTarget Is Nothing Then
        For Each RngI In newTarget
            'ADD TO COLLECTION
            'ITEM, KEY
            collOldValues.add Array(RngI.value, RngI.formula), RngI.Address
        Next RngI
    End If
done:
        Exit Sub
endWithError:
        DisplayError Err, "trackChanges_loadOldValues_toCollection", Erl
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
vNEW = Target.Value
aNEW = Target.Address
Application.EnableEvents = False
Application.Undo
vOLD = Target.Value
Target.Value = vNEW
Application.EnableEvents = True
End Sub

Using Static will solve your problem (with some other stuff to initialize old_value properly:使用Static将解决您的问题(使用其他一些东西来正确初始化old_value

Private Sub Worksheet_Change(ByVal Target As Range)
    Static old_value As String
    Dim inited as Boolean 'Used to detect first call and fill old_value
    Dim new_value As String
    If Not Intersect(cell, Range("cell_of_interest")) Is Nothing Then
         new_value = Range("cell_of_interest").Value
         If Not inited Then
             inited = True
         Else
            Call DoFoo (old_value, new_value)
        End If
        old_value = new_value
    Next cell
End Sub

In workbook code, force call of Worksheet_change to fill old_value :在工作簿代码中,强制调用Worksheet_change来填充old_value

Private Sub Private Sub Workbook_Open()
     SheetX.Worksheet_Change SheetX.Range("cell_of_interest")
End Sub

Note, however, that ANY solution based in VBA variables (including dictionary and another more sophisticate methods) will fail if you stop (Reset) running code (eg. while creating new macros, debugging some code, ...).但是请注意,如果您停止(重置)正在运行的代码(例如,在创建新宏、调试一些代码时……),任何基于 VBA 变量(包括字典和其他更复杂的方法)的解决方案都将失败。 To avoid such, consider using alternative storage methods (hidden worksheet, for example).为避免这种情况,请考虑使用其他存储方法(例如,隐藏工作表)。

I have read this old post, and I would like to provide another solution.我已经阅读了这篇旧帖子,我想提供另一种解决方案。

The problem with running Application.Undo is that Woksheet_Change runs again.运行 Application.Undo 的问题是 Woksheet_Change 再次运行。 We have the same problem when we restore.当我们恢复时,我们遇到了同样的问题。

To avoid that, I use a piece of code to avoid the second steps through Worksheet_Change.为了避免这种情况,我使用一段代码来避免通过 Worksheet_Change 执行第二个步骤。

Before we begin, we must create a Boolean static variable BlnAlreadyBeenHere, to tell Excel not to run Worksheet_Change again在我们开始之前,我们必须创建一个布尔静态变量 BlnAlreadyBeenHere,告诉 Excel 不要再次运行 Worksheet_Change

Here you can see it:在这里你可以看到它:

    Private Sub Worksheet_Change(ByVal Target As Range)

    Static blnAlreadyBeenHere As Boolean

    'This piece avoid to execute Worksheet_Change again
    If blnAlreadyBeenHere Then
        blnAlreadyBeenHere = False
        Exit Sub
    End If

    'Now, we will store the old and new value
    Dim vOldValue As Variant
    Dim vNewValue As Variant

    'To store new value
    vNewValue = Target.Value

    'Undo to retrieve old value

    'To avoid new Worksheet_Change execution
    blnAlreadyBeenHere = True

    Application.Undo

    'To store old value
    vOldValue = Target.Value

    'To rewrite new value

    'To avoid new Worksheet_Change execution agein
    blnAlreadyBeenHere = True
    Target.Value = vNewValue

    'Done! I've two vaules stored
    Debug.Print vOldValue, vNewValue

End Sub

The advantage of this method is that it is not necessary to run Worksheet_SelectionChange.这种方法的优点是不需要运行 Worksheet_SelectionChange。

If we want the routine to work from another module, we just have to take the declaration of the variable blnAlreadyBeenHere out of the routine, and declare it with Dim.如果我们希望例程从另一个模块工作,我们只需要从例程中取出变量 blnAlreadyBeenHere 的声明,并用 Dim 声明它。

Same operation with vOldValue and vNewValue, in the header of a module与 vOldValue 和 vNewValue 相同的操作,在模块的头部

Dim blnAlreadyBeenHere As Boolean
Dim vOldValue As Variant
Dim vNewValue As Variant

Just a thought, but Have you tried using application.undo只是一个想法,但是您是否尝试过使用 application.undo

This will set the values back again.这将再次设置这些值。 You can then simply read the original value.然后您可以简单地读取原始值。 It should not be too difficult to store the new values first, so you change them back again if you like.首先存储新值应该不会太困难,因此如果您愿意,可以再次更改它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Excel VBA:更改单元格后,如何将SpecialSpecial粘贴到单元格位置的同一行? - Excel VBA: How do I PasteSpecial into same row of Cell location after Cell is changed? Excel VBA-在Excel中手动更改单元格的旧值时的日志更改 - Excel VBA - Log changes when old value of a cell is changed manually in Excel 如何使用 VBA 从 Excel 中的公式中获取单元格值? - How do I get the cell value from a formula in Excel using VBA? 如何检查单元格的内容是否为数组中的值-VBA Excel - How do I check if the contents of a cell is a value in an array - VBA Excel 如何按照工作表中的格式在 VBA 中检索 Excel 单元格值? - How do I retrieve an Excel cell value in VBA as formatted in the WorkSheet? 每次单元格获取的值更改时,如何运行VBA代码?如何将代码放在哪里? - How can I run a VBA code each time a cell get is value changed and where do I put it the code? 如何使用VBA在excel单元中获取月份和日期? - How do I get the month and day in excel cell using VBA? 如何在Excel VBA中获取单元格的位置范围? - how do I get a cell's location as a range in excel vba? 如果值小于旧值| excel | VBA |,如何清除列并再次填充数据 - How do i clear column and fill data again if value is less than old value |excel|VBA| VBA如何获取单元格值作为模块级别常量? - VBA How do I get a cell value as module level constant?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM