Kỹ thuật Undo trong VBA

  • Thread starter vothanhthu
  • Ngày gửi
V

vothanhthu

Guest
1. Giới thiệu
Như tất cả chúng ta đều biết, trong lập trình VBA chúng ta cần phải cân nhắc thật cẩn thận khi chạy một code liên quan đến gán giá trị vào sheet, vì khi chạy các giá trị được gán sheet vào sẽ không thể quay trở lại. Với lý do đó, hôm nay mình xin giới thiệu đến các bạn một kỹ thuật Undo lại các giá trị, tính chất của các đối tượng khi thay đổi bằng VBA

Kỹ thuật này mình trích dẫn nguồn từ bạn Jan Karel Pieterse thuộc JKP.
2. Code Undo trong Class Module
Tạo một Class Module tên clsExecAndUndo
Mã:
Option Explicit

Private mcolUndoObjects As Collection
Private mUndoObject As clsUndoObject

Public Function AddAndProcessObject(oObj As Object, sProperty As String, vValue As Variant) As Boolean
    Set mUndoObject = New clsUndoObject
    With mUndoObject
        Set .ObjectToChange = oObj
        .NewValue = vValue
        .PropertyToChange = sProperty
        mcolUndoObjects.Add mUndoObject
        If .ExecuteCommand = True Then
            AddAndProcessObject = True
        Else
            AddAndProcessObject = False
        End If
    End With
End Function

Private Sub Class_Initialize()
    Set mcolUndoObjects = New Collection
End Sub

Private Sub Class_Terminate()
    ResetUndo
End Sub

Public Sub ResetUndo()
    While mcolUndoObjects.Count > 0
        mcolUndoObjects.Remove (1)
    Wend
    Set mUndoObject = Nothing
End Sub

Public Sub UndoAll()
    Dim lCount As Long
    '    On Error Resume Next
    For lCount = mcolUndoObjects.Count To 1 Step -1
        Set mUndoObject = mcolUndoObjects(lCount)
        mUndoObject.UndoChange
        Set mUndoObject = Nothing
    Next
    ResetUndo
End Sub

Public Sub UndoLast()
    Dim lCount As Long
    '    On Error Resume Next
    If mcolUndoObjects.Count >= 1 Then
        Set mUndoObject = mcolUndoObjects(mcolUndoObjects.Count)
        mUndoObject.UndoChange
        mcolUndoObjects.Remove mcolUndoObjects.Count
        Set mUndoObject = Nothing
    Else
        ResetUndo
    End If
End Sub

Public Function UndoCount() As Long
    UndoCount = mcolUndoObjects.Count
End Function
Tạo Class Module nữa đặt tên clsUndoObject
Mã:
Option Explicit

Private mUndoObject As Object
Private msProperty As String
Private mvNewValue As Variant
Private mvOldValue As Variant

Public Property Let PropertyToChange(sProperty As String)
    msProperty = sProperty
End Property

Public Property Get PropertyToChange() As String
    PropertyToChange = msProperty
End Property

Public Property Set ObjectToChange(oObj As Object)
    Set mUndoObject = oObj
End Property

Public Property Get ObjectToChange() As Object
    Set ObjectToChange = mUndoObject
End Property

Public Property Let NewValue(vValue As Variant)
    mvNewValue = vValue
End Property

Public Property Get NewValue() As Variant
    NewValue = mvNewValue
End Property

Public Property Let OldValue(vValue As Variant)
    mvOldValue = vValue
End Property

Public Property Get OldValue() As Variant
    OldValue = mvOldValue
End Property

Public Function ExecuteCommand() As Boolean
    ExecuteCommand = False
    If mUndoObject Is Nothing Then
    End If
    If mvNewValue = "" Then
    End If
    If msProperty = "" Then
    End If
    If GetOldValue Then
        SetNewValue
        ExecuteCommand = True
    Else
        'Failed to retrieve old value!
    End If
End Function

Private Function GetOldValue() As Boolean
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    Set oTemp = ObjectToChange
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    OldValue = CallByName(oTemp, vProps(lProps), VbGet)
    If Err.Number = 0 Then
        GetOldValue = True
    Else
        GetOldValue = False
    End If
End Function

Private Function SetNewValue() As Boolean
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    Dim vResult As Variant
    Err.Clear
    Set oTemp = ObjectToChange
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    vResult = CallByName(oTemp, vProps(lProps), VbLet, NewValue)
    If Err.Number = 0 Then
        SetNewValue = True
    Else
        SetNewValue = False
    End If
End Function

Public Function UndoChange()
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    Dim vResult As Variant
    Set oTemp = ObjectToChange
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    vResult = CallByName(oTemp, vProps(lProps), VbLet, OldValue)
    If vResult <> "" Then
        UndoChange = True
    Else
        UndoChange = False
    End If
End Function
3. Hướng dẫn sử dụng
Ví dụ:
Mình có các dữ liệu như sau:
- Vùng A2:B6 có lần lượt các giá trị từ 1 đến 10;
- Ô D2 đang có màu xanh;
- Và mình có thêm một Shapes (Xanh viền cam) đặt tên là Shapes1.
Bạn cần đăng nhập để thấy đính kèm

Mình sẽ thêm đoạn code này vào để đổi vùng A2:B6 thành 10, D2 thành màu đỏ và màu Shapes1 thành màu đỏ luôn.
Trước tiên mình cần khai báo hàm ở đầu Module
Mã:
Option Explicit
Dim mUndoClass As clsExecAndUndo
Sau đó thực hiện các thay đổi trong Sub Change
Mã:
Sub Change()
If mUndoClass Is Nothing Then
        Set mUndoClass = New clsExecAndUndo
Else
        'Loai bo Undo truoc do
        Set mUndoClass = Nothing
        Set mUndoClass = New clsExecAndUndo
End If
mUndoClass.AddAndProcessObject ActiveSheet.Range("A2:B6"), "Value", 10 'Thay doi A2:B6 thanh 10
mUndoClass.AddAndProcessObject ActiveSheet.Range("D2"), "Interior.Colorindex", 3 'Doi mau D2 thanh mau Do
mUndoClass.AddAndProcessObject ActiveSheet.Shapes("Shapes1"), "Fill.ForeColor.SchemeColor", 10 'Doi mau Shape1 thanh mau do
End Sub
Ở đây, đoạn code If ... End sẽ được bắt buộc thêm vào trước mỗi sub để gọi Class Module. Và khi gán thuộc tính, giá trị vào các đối tượng trên sheet chúng ta sẽ dùng lệnh sau đây, lệnh này sẽ lưu lại các tính chất, giá trị của đối tượng trước khi thay đổi bởi code
mUndoClass.AddAndProcessObject Đối_Tượng,"Tính chất",Giá trị
Trong đó:
Đối_Tượng: là các cell, shapes mà chúng ta muốn thay đổi giá trị, tính chất.
Tính chất: Là tính chất của Đối tượng, lưu ý để trong dấu ngoặc kép (""), Ví dụ: "Value","Height"...
Giá trị:
Giá trị của các tính chất cần thay đổi.

Dễ hiểu hơn, Bình thường mình muốn thay đổi giá trị tại A2:B6 thì mình sẽ dùng lệnh
Mã:
Activesheet.Range("A2:B6").value = 10
Giờ để Undo được ta sẽ dùng một lệnh đặc biệt như sau
Mã:
mUndoClass.AddAndProcessObject ActiveSheet.Range("A2:B6"), "Value", 10
Giờ mình tạo thêm một Sub để thực hiện chức năng Undo đặt tên luôn là Undo
Mã:
Sub Undo()
    If mUndoClass Is Nothing Then Exit Sub
    mUndoClass.UndoAll
    Set mUndoClass = Nothing
End Sub
Và đây là kết quả
Bạn cần đăng nhập để thấy đính kèm

4. Những điểm cần lưu ý
Khi thực hiện Undo bằng kỹ thuật này chúng ta chỉ có thể Undo được lần thay đổi code gần nhất của đối tượng đó, Ví dụ chúng ta nhấn 2 lần Sub Change thì không thể Undo được nữa.

Mỗi khi muốn Undo gì thì chúng ta phải có chủ ý ngay từ trước bằng cách thêm vào code, không thể cứ Ctrl + Z như bình thường.

Code sẽ rất hữu hiệu trong việc thực hiện những chương trình mà người sử dụng code cần Undo nhiều khi thao tác.

Trong nguồn tham khảo, tác giả ghi rất rõ ý tưởng code, các bạn xem tham khảo nha. Nguồn tham khảo:

Mình sẽ rất vui nếu như chia sẽ của mình đã mang lại cho bạn thêm một thông tin mới về VBA. Nếu có thắc mắc, khó khăn gì thì các bạn comment bên dưới phần trả lời, chúng ta cũng thảo luận nhé !
 
Sửa lần cuối bởi điều hành viên:

chisinhvnn

Yêu THVBA
1. Giới thiệu
Như tất cả chúng ta đều biết, trong lập trình VBA chúng ta cần phải cân nhắc thật cẩn thận khi chạy một code liên quan đến gán giá trị vào sheet, vì khi chạy các giá trị được gán sheet vào sẽ không thể quay trở lại. Với lý do đó, hôm nay mình xin giới thiệu đến các bạn một kỹ thuật Undo lại các giá trị, tính chất của các đối tượng khi thay đổi bằng VBA

Kỹ thuật này mình trích dẫn nguồn từ bạn Jan Karel Pieterse thuộc JKP.
2. Code Undo trong Class Module
Tạo một Class Module tên clsExecAndUndo
Mã:
Option Explicit

Private mcolUndoObjects As Collection
Private mUndoObject As clsUndoObject

Public Function AddAndProcessObject(oObj As Object, sProperty As String, vValue As Variant) As Boolean
    Set mUndoObject = New clsUndoObject
    With mUndoObject
        Set .ObjectToChange = oObj
        .NewValue = vValue
        .PropertyToChange = sProperty
        mcolUndoObjects.Add mUndoObject
        If .ExecuteCommand = True Then
            AddAndProcessObject = True
        Else
            AddAndProcessObject = False
        End If
    End With
End Function

Private Sub Class_Initialize()
    Set mcolUndoObjects = New Collection
End Sub

Private Sub Class_Terminate()
    ResetUndo
End Sub

Public Sub ResetUndo()
    While mcolUndoObjects.Count > 0
        mcolUndoObjects.Remove (1)
    Wend
    Set mUndoObject = Nothing
End Sub

Public Sub UndoAll()
    Dim lCount As Long
    '    On Error Resume Next
    For lCount = mcolUndoObjects.Count To 1 Step -1
        Set mUndoObject = mcolUndoObjects(lCount)
        mUndoObject.UndoChange
        Set mUndoObject = Nothing
    Next
    ResetUndo
End Sub

Public Sub UndoLast()
    Dim lCount As Long
    '    On Error Resume Next
    If mcolUndoObjects.Count >= 1 Then
        Set mUndoObject = mcolUndoObjects(mcolUndoObjects.Count)
        mUndoObject.UndoChange
        mcolUndoObjects.Remove mcolUndoObjects.Count
        Set mUndoObject = Nothing
    Else
        ResetUndo
    End If
End Sub

Public Function UndoCount() As Long
    UndoCount = mcolUndoObjects.Count
End Function
Tạo Class Module nữa đặt tên clsUndoObject
Mã:
Option Explicit

Private mUndoObject As Object
Private msProperty As String
Private mvNewValue As Variant
Private mvOldValue As Variant

Public Property Let PropertyToChange(sProperty As String)
    msProperty = sProperty
End Property

Public Property Get PropertyToChange() As String
    PropertyToChange = msProperty
End Property

Public Property Set ObjectToChange(oObj As Object)
    Set mUndoObject = oObj
End Property

Public Property Get ObjectToChange() As Object
    Set ObjectToChange = mUndoObject
End Property

Public Property Let NewValue(vValue As Variant)
    mvNewValue = vValue
End Property

Public Property Get NewValue() As Variant
    NewValue = mvNewValue
End Property

Public Property Let OldValue(vValue As Variant)
    mvOldValue = vValue
End Property

Public Property Get OldValue() As Variant
    OldValue = mvOldValue
End Property

Public Function ExecuteCommand() As Boolean
    ExecuteCommand = False
    If mUndoObject Is Nothing Then
    End If
    If mvNewValue = "" Then
    End If
    If msProperty = "" Then
    End If
    If GetOldValue Then
        SetNewValue
        ExecuteCommand = True
    Else
        'Failed to retrieve old value!
    End If
End Function

Private Function GetOldValue() As Boolean
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    Set oTemp = ObjectToChange
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    OldValue = CallByName(oTemp, vProps(lProps), VbGet)
    If Err.Number = 0 Then
        GetOldValue = True
    Else
        GetOldValue = False
    End If
End Function

Private Function SetNewValue() As Boolean
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    Dim vResult As Variant
    Err.Clear
    Set oTemp = ObjectToChange
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    vResult = CallByName(oTemp, vProps(lProps), VbLet, NewValue)
    If Err.Number = 0 Then
        SetNewValue = True
    Else
        SetNewValue = False
    End If
End Function

Public Function UndoChange()
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    Dim vResult As Variant
    Set oTemp = ObjectToChange
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    vResult = CallByName(oTemp, vProps(lProps), VbLet, OldValue)
    If vResult <> "" Then
        UndoChange = True
    Else
        UndoChange = False
    End If
End Function
3. Hướng dẫn sử dụng
Ví dụ:
Mình có các dữ liệu như sau:
- Vùng A2:B6 có lần lượt các giá trị từ 1 đến 10;
- Ô D2 đang có màu xanh;
- Và mình có thêm một Shapes (Xanh viền cam) đặt tên là Shapes1.
Bạn cần đăng nhập để thấy đính kèm

Mình sẽ thêm đoạn code này vào để đổi vùng A2:B6 thành 10, D2 thành màu đỏ và màu Shapes1 thành màu đỏ luôn.
Trước tiên mình cần khai báo hàm ở đầu Module
Mã:
Option Explicit
Dim mUndoClass As clsExecAndUndo
Sau đó thực hiện các thay đổi trong Sub Change
Mã:
Sub Change()
If mUndoClass Is Nothing Then
        Set mUndoClass = New clsExecAndUndo
Else
        'Loai bo Undo truoc do
        Set mUndoClass = Nothing
        Set mUndoClass = New clsExecAndUndo
End If
mUndoClass.AddAndProcessObject ActiveSheet.Range("A2:B6"), "Value", 10 'Thay doi A2:B6 thanh 10
mUndoClass.AddAndProcessObject ActiveSheet.Range("D2"), "Interior.Colorindex", 3 'Doi mau D2 thanh mau Do
mUndoClass.AddAndProcessObject ActiveSheet.Shapes("Shapes1"), "Fill.ForeColor.SchemeColor", 10 'Doi mau Shape1 thanh mau do
End Sub
Ở đây, đoạn code If ... End sẽ được bắt buộc thêm vào trước mỗi sub để gọi Class Module. Và khi gán thuộc tính, giá trị vào các đối tượng trên sheet chúng ta sẽ dùng lệnh sau đây, lệnh này sẽ lưu lại các tính chất, giá trị của đối tượng trước khi thay đổi bởi code
mUndoClass.AddAndProcessObject Đối_Tượng,"Tính chất",Giá trị
Trong đó:
Đối_Tượng: là các cell, shapes mà chúng ta muốn thay đổi giá trị, tính chất.
Tính chất: Là tính chất của Đối tượng, lưu ý để trong dấu ngoặc kép (""), Ví dụ: "Value","Height"...
Giá trị:
Giá trị của các tính chất cần thay đổi.

Dễ hiểu hơn, Bình thường mình muốn thay đổi giá trị tại A2:B6 thì mình sẽ dùng lệnh
Mã:
Activesheet.Range("A2:B6").value = 10
Giờ để Undo được ta sẽ dùng một lệnh đặc biệt như sau
Mã:
mUndoClass.AddAndProcessObject ActiveSheet.Range("A2:B6"), "Value", 10
Giờ mình tạo thêm một Sub để thực hiện chức năng Undo đặt tên luôn là Undo
Mã:
Sub Undo()
    If mUndoClass Is Nothing Then Exit Sub
    mUndoClass.UndoAll
    Set mUndoClass = Nothing
End Sub
Và đây là kết quả
Bạn cần đăng nhập để thấy đính kèm

4. Những điểm cần lưu ý
Khi thực hiện Undo bằng kỹ thuật này chúng ta chỉ có thể Undo được lần thay đổi code gần nhất của đối tượng đó, Ví dụ chúng ta nhấn 2 lần Sub Change thì không thể Undo được nữa.

Mỗi khi muốn Undo gì thì chúng ta phải có chủ ý ngay từ trước bằng cách thêm vào code, không thể cứ Ctrl + Z như bình thường.

Code sẽ rất hữu hiệu trong việc thực hiện những chương trình mà người sử dụng code cần Undo nhiều khi thao tác.

Trong nguồn tham khảo, tác giả ghi rất rõ ý tưởng code, các bạn xem tham khảo nha. Nguồn tham khảo:

Mình sẽ rất vui nếu như chia sẽ của mình đã mang lại cho bạn thêm một thông tin mới về VBA. Nếu có thắc mắc, khó khăn gì thì các bạn comment bên dưới phần trả lời, chúng ta cũng thảo luận nhé !
Bài này hay và thiết thực, @vothanhthu có hướng dẫn bên GPE nhưng chưa làm được. tải file về nghiên cứu lại.
 

salomon

Yêu THVBA
1. Giới thiệu
Như tất cả chúng ta đều biết, trong lập trình VBA chúng ta cần phải cân nhắc thật cẩn thận khi chạy một code liên quan đến gán giá trị vào sheet, vì khi chạy các giá trị được gán sheet vào sẽ không thể quay trở lại. Với lý do đó, hôm nay mình xin giới thiệu đến các bạn một kỹ thuật Undo lại các giá trị, tính chất của các đối tượng khi thay đổi bằng VBA

Kỹ thuật này mình trích dẫn nguồn từ bạn Jan Karel Pieterse thuộc JKP.
2. Code Undo trong Class Module
Tạo một Class Module tên clsExecAndUndo
Mã:
Option Explicit

Private mcolUndoObjects As Collection
Private mUndoObject As clsUndoObject

Public Function AddAndProcessObject(oObj As Object, sProperty As String, vValue As Variant) As Boolean
    Set mUndoObject = New clsUndoObject
    With mUndoObject
        Set .ObjectToChange = oObj
        .NewValue = vValue
        .PropertyToChange = sProperty
        mcolUndoObjects.Add mUndoObject
        If .ExecuteCommand = True Then
            AddAndProcessObject = True
        Else
            AddAndProcessObject = False
        End If
    End With
End Function

Private Sub Class_Initialize()
    Set mcolUndoObjects = New Collection
End Sub

Private Sub Class_Terminate()
    ResetUndo
End Sub

Public Sub ResetUndo()
    While mcolUndoObjects.Count > 0
        mcolUndoObjects.Remove (1)
    Wend
    Set mUndoObject = Nothing
End Sub

Public Sub UndoAll()
    Dim lCount As Long
    '    On Error Resume Next
    For lCount = mcolUndoObjects.Count To 1 Step -1
        Set mUndoObject = mcolUndoObjects(lCount)
        mUndoObject.UndoChange
        Set mUndoObject = Nothing
    Next
    ResetUndo
End Sub

Public Sub UndoLast()
    Dim lCount As Long
    '    On Error Resume Next
    If mcolUndoObjects.Count >= 1 Then
        Set mUndoObject = mcolUndoObjects(mcolUndoObjects.Count)
        mUndoObject.UndoChange
        mcolUndoObjects.Remove mcolUndoObjects.Count
        Set mUndoObject = Nothing
    Else
        ResetUndo
    End If
End Sub

Public Function UndoCount() As Long
    UndoCount = mcolUndoObjects.Count
End Function
Tạo Class Module nữa đặt tên clsUndoObject
Mã:
Option Explicit

Private mUndoObject As Object
Private msProperty As String
Private mvNewValue As Variant
Private mvOldValue As Variant

Public Property Let PropertyToChange(sProperty As String)
    msProperty = sProperty
End Property

Public Property Get PropertyToChange() As String
    PropertyToChange = msProperty
End Property

Public Property Set ObjectToChange(oObj As Object)
    Set mUndoObject = oObj
End Property

Public Property Get ObjectToChange() As Object
    Set ObjectToChange = mUndoObject
End Property

Public Property Let NewValue(vValue As Variant)
    mvNewValue = vValue
End Property

Public Property Get NewValue() As Variant
    NewValue = mvNewValue
End Property

Public Property Let OldValue(vValue As Variant)
    mvOldValue = vValue
End Property

Public Property Get OldValue() As Variant
    OldValue = mvOldValue
End Property

Public Function ExecuteCommand() As Boolean
    ExecuteCommand = False
    If mUndoObject Is Nothing Then
    End If
    If mvNewValue = "" Then
    End If
    If msProperty = "" Then
    End If
    If GetOldValue Then
        SetNewValue
        ExecuteCommand = True
    Else
        'Failed to retrieve old value!
    End If
End Function

Private Function GetOldValue() As Boolean
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    Set oTemp = ObjectToChange
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    OldValue = CallByName(oTemp, vProps(lProps), VbGet)
    If Err.Number = 0 Then
        GetOldValue = True
    Else
        GetOldValue = False
    End If
End Function

Private Function SetNewValue() As Boolean
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    Dim vResult As Variant
    Err.Clear
    Set oTemp = ObjectToChange
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    vResult = CallByName(oTemp, vProps(lProps), VbLet, NewValue)
    If Err.Number = 0 Then
        SetNewValue = True
    Else
        SetNewValue = False
    End If
End Function

Public Function UndoChange()
    Dim oTemp As Object
    Dim lCount As Long
    Dim lProps As Long
    Dim vProps As Variant
    Dim vResult As Variant
    Set oTemp = ObjectToChange
    vProps = Split(PropertyToChange, ".")
    lProps = UBound(vProps)
    For lCount = 0 To lProps - 1
        Set oTemp = CallByName(oTemp, vProps(lCount), VbGet)
    Next
    If TypeOf oTemp Is Range Then
        If LCase(vProps(lProps)) = "value" Then
            vProps(lProps) = "Formula"
        End If
    End If
    vResult = CallByName(oTemp, vProps(lProps), VbLet, OldValue)
    If vResult <> "" Then
        UndoChange = True
    Else
        UndoChange = False
    End If
End Function
3. Hướng dẫn sử dụng
Ví dụ:
Mình có các dữ liệu như sau:
- Vùng A2:B6 có lần lượt các giá trị từ 1 đến 10;
- Ô D2 đang có màu xanh;
- Và mình có thêm một Shapes (Xanh viền cam) đặt tên là Shapes1.
Bạn cần đăng nhập để thấy đính kèm

Mình sẽ thêm đoạn code này vào để đổi vùng A2:B6 thành 10, D2 thành màu đỏ và màu Shapes1 thành màu đỏ luôn.
Trước tiên mình cần khai báo hàm ở đầu Module
Mã:
Option Explicit
Dim mUndoClass As clsExecAndUndo
Sau đó thực hiện các thay đổi trong Sub Change
Mã:
Sub Change()
If mUndoClass Is Nothing Then
        Set mUndoClass = New clsExecAndUndo
Else
        'Loai bo Undo truoc do
        Set mUndoClass = Nothing
        Set mUndoClass = New clsExecAndUndo
End If
mUndoClass.AddAndProcessObject ActiveSheet.Range("A2:B6"), "Value", 10 'Thay doi A2:B6 thanh 10
mUndoClass.AddAndProcessObject ActiveSheet.Range("D2"), "Interior.Colorindex", 3 'Doi mau D2 thanh mau Do
mUndoClass.AddAndProcessObject ActiveSheet.Shapes("Shapes1"), "Fill.ForeColor.SchemeColor", 10 'Doi mau Shape1 thanh mau do
End Sub
Ở đây, đoạn code If ... End sẽ được bắt buộc thêm vào trước mỗi sub để gọi Class Module. Và khi gán thuộc tính, giá trị vào các đối tượng trên sheet chúng ta sẽ dùng lệnh sau đây, lệnh này sẽ lưu lại các tính chất, giá trị của đối tượng trước khi thay đổi bởi code
mUndoClass.AddAndProcessObject Đối_Tượng,"Tính chất",Giá trị
Trong đó:
Đối_Tượng: là các cell, shapes mà chúng ta muốn thay đổi giá trị, tính chất.
Tính chất: Là tính chất của Đối tượng, lưu ý để trong dấu ngoặc kép (""), Ví dụ: "Value","Height"...
Giá trị:
Giá trị của các tính chất cần thay đổi.

Dễ hiểu hơn, Bình thường mình muốn thay đổi giá trị tại A2:B6 thì mình sẽ dùng lệnh
Mã:
Activesheet.Range("A2:B6").value = 10
Giờ để Undo được ta sẽ dùng một lệnh đặc biệt như sau
Mã:
mUndoClass.AddAndProcessObject ActiveSheet.Range("A2:B6"), "Value", 10
Giờ mình tạo thêm một Sub để thực hiện chức năng Undo đặt tên luôn là Undo
Mã:
Sub Undo()
    If mUndoClass Is Nothing Then Exit Sub
    mUndoClass.UndoAll
    Set mUndoClass = Nothing
End Sub
Và đây là kết quả
Bạn cần đăng nhập để thấy đính kèm

4. Những điểm cần lưu ý
Khi thực hiện Undo bằng kỹ thuật này chúng ta chỉ có thể Undo được lần thay đổi code gần nhất của đối tượng đó, Ví dụ chúng ta nhấn 2 lần Sub Change thì không thể Undo được nữa.

Mỗi khi muốn Undo gì thì chúng ta phải có chủ ý ngay từ trước bằng cách thêm vào code, không thể cứ Ctrl + Z như bình thường.

Code sẽ rất hữu hiệu trong việc thực hiện những chương trình mà người sử dụng code cần Undo nhiều khi thao tác.

Trong nguồn tham khảo, tác giả ghi rất rõ ý tưởng code, các bạn xem tham khảo nha. Nguồn tham khảo:

Mình sẽ rất vui nếu như chia sẽ của mình đã mang lại cho bạn thêm một thông tin mới về VBA. Nếu có thắc mắc, khó khăn gì thì các bạn comment bên dưới phần trả lời, chúng ta cũng thảo luận nhé !
ad cho em hỏi là giờ em không cố định giá trị là 10 nữa, mà là một giá trị thay đổi được. ví dụ lần click đầu tiên là 10, lần click thứ 2 là 15, lần click thứ 3 là 20, tức là cứ sau mỗi một lần click thì giá trị tăng thêm 5 đơn vị nữa. thì đoạn code này có sử dụng được không ạ?
 
Top