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.
The class: 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:
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.
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.
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. One of the four is shown below. The only difference is the collection used in the ADD and EXIST calls.
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. 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
Добрый день, Ув. Форумчане! Прошу подсказать решение следующей задачи: Прикрепленные файлы
|
|
webley Пользователь Сообщений: 1995 |
#2 03.02.2022 11:36:08 Добрый день. А если без Worksheet_SelectionChange, вот так?
|
||
ANDREY Пользователь Сообщений: 24 |
|
Спасибо за вариант, но при первом запуске это работать не будет, т.к. переменная СтароеЗначение изначально будет пустой. |
|
webley Пользователь Сообщений: 1995 |
ну тогда можно совместить — первоначальное значение присваивать, например, через Worksheet_SelectionChange (хотя можно при активации листа/открытии книги), а изменение уже отслеживать «по факту» в процедуре Worksheet_Change |
ANDREY Пользователь Сообщений: 24 |
#6 03.02.2022 12:26:05
Данная переменная перестает быть пустой при выделении или активации ячейки. Если в этой ячейке отсутствует значение, то значит предыдущее значение «». И это можно обработать. |
||
vikttur Пользователь Сообщений: 47199 |
#7 03.02.2022 12:26:26 В Worksheet_Change
|
||
Alexey0185 Пользователь Сообщений: 133 |
#8 03.02.2022 12:28:01 ANDREY, в том и проблема, что через Worksheet_SelectionChange значение присваивается, если ячейка до этого не была активной. А если она в данный момент активна, то Worksheet_SelectionChange не срабатывает.
Благодарю!!! Работает! |
||
Alexey0185 Пользователь Сообщений: 133 |
#9 03.02.2022 12:33:13
Благодарю!!! Работает! ANDREY, Изменено: vikttur — 03.02.2022 12:41:53 |
||
БМВ Модератор Сообщений: 21378 Excel 2013, 2016 |
#10 03.02.2022 12:41:41 Alexey0185, смотрите что написал vikttur.
или можно с трудом натянуть на неразрывный диапазон, но сейчас если скопировать что-то в A1:b1, то сломается. ну и тут не простой но вариант По вопросам из тем форума, личку не читаю. |
||
vikttur Пользователь Сообщений: 47199 |
#11 03.02.2022 12:45:14 Не схитрил, а предложил
И про application забыл… |
||
Alexey0185 Пользователь Сообщений: 133 |
#12 03.02.2022 12:58:40 БМВ
, благодарю!
Про application я и сам догуглил. Направление задали верное — это главное. |
||
БМВ Модератор Сообщений: 21378 Excel 2013, 2016 |
#13 03.02.2022 13:08:03 Уппс, а я вот никогда не пользовал undo undo и думал что по стеку идет в одном направлении, а оно именно одно действие отменяет, при этом сама отмена становится последним…
По вопросам из тем форума, личку не читаю. |
||
_Igor_61 Пользователь Сообщений: 3007 |
Михаил, СПАСИБО!!! . Изменено: _Igor_61 — 03.02.2022 15:44:02 |
Достаточно давно я описывал данный подход в статье: Выделение сделанных изменений только возвращал я свойство .Formula, т.к. это чуть более правильно в случаях, если изменения вносились в ячейку с формулой. Ну и еще парочка дополнений там есть, вроде возврата выделенных ячеек в исходное положение, т.к. после Undo выделенная область тоже может смениться. Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы… |
|
sokol92 Пользователь Сообщений: 4445 |
Здравствуйте, коллеги! Изменено: sokol92 — 03.02.2022 17:55:42 |
sokol92, что-то у меня не получилось повторить. Сделал четко по озвученному алгоритму. Ни сообщения, ни ошибку не получил. Что делаю не так? Изменено: Дмитрий(The_Prist) Щербаков — 03.02.2022 17:59:36 Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы… |
|
sokol92 Пользователь Сообщений: 4445 |
Дмитрий, не знаю — я раз 20 проверял. Важно нажимать именно Backspace, а не Del. Изменено: sokol92 — 03.02.2022 18:11:29 |
Дмитрий(The_Prist) Щербаков Пользователь Сообщений: 14182 Профессиональная разработка приложений для MS Office |
#19 03.02.2022 18:16:56
вот теперь понятно. Дело-то по сути в проверке данных и не Undo проблему создает, а проверка данных, т.к. Und-ить нечего
Но можно и при любой ошибке выход делать Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы… |
||||
sokol92 Пользователь Сообщений: 4445 |
#20 03.02.2022 18:22:34
Да. Там событие изменения ячейки A1 вызывается дважды. P.S. Ошибка исчезновения имени при цитировании не исправлена. Все время забываю нажимать на «BB code». Изменено: sokol92 — 03.02.2022 18:25:54 Владимир |
||
БМВ Модератор Сообщений: 21378 Excel 2013, 2016 |
#21 03.02.2022 19:48:01
там тоже On Error Resume Next Спасибо за тест кейс. ДА все верно, нужно обрабатывать ошибки подобные. Я тут на досуге посмотрел вариант для подобного контроля в диапазоне на случай копирования диапазона. ну собственно идея то примитивная, но приходится сохранять адреса, ячеек измененных и их значение в массив а потом по нему уже все тоже самое. По вопросам из тем форума, личку не читаю. |
||
Обычно при обновлении ячейки новым содержимым предыдущее значение будет закрыто, если не отменить операцию в Excel. Однако, если вы хотите сохранить предыдущее значение для сравнения с обновленным, сохранение предыдущего значения ячейки в другую ячейку или в комментарий к ячейке будет хорошим выбором. Метод, описанный в этой статье, поможет вам в этом.
Сохранить предыдущее значение ячейки с кодом VBA в Excel
Сохранить предыдущее значение ячейки с кодом VBA в Excel
Предположим, у вас есть таблица, как показано на скриншоте ниже. Если какая-либо ячейка в столбце C изменилась, вы хотите сохранить ее предыдущее значение в соответствующей ячейке столбца G или автоматически сохранить в комментарии. Для этого сделайте следующее.
1. На рабочем листе содержится значение, которое вы сохраните при обновлении, щелкните правой кнопкой мыши вкладку листа и выберите Просмотреть код из контекстного меню. Смотрите скриншот:
2. В дебюте Microsoft Visual Basic для приложений Скопируйте приведенный ниже код VBA в окно кода.
Следующий код VBA помогает сохранить предыдущее значение ячейки указанного столбца в другой столбец.
Код VBA: сохранить предыдущее значение ячейки в другую ячейку столбца
Dim xRg As Range
Dim xChangeRg As Range
Dim xDependRg As Range
Dim xDic As New Dictionary
Private Sub Worksheet_Change(ByVal Target As Range)
Dim I As Long
Dim xCell As Range
Dim xDCell As Range
Dim xHeader As String
Dim xCommText As String
On Error Resume Next
Application.ScreenUpdating = False
Application.EnableEvents = False
xHeader = "Previous value :"
x = xDic.Keys
For I = 0 To UBound(xDic.Keys)
Set xCell = Range(xDic.Keys(I))
Set xDCell = Cells(xCell.Row, 7)
xDCell.Value = ""
xDCell.Value = xDic.Items(I)
Next
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim I, J As Long
Dim xRgArea As Range
On Error GoTo Label1
If Target.Count > 1 Then Exit Sub
Application.EnableEvents = False
Set xDependRg = Target.Dependents
If xDependRg Is Nothing Then GoTo Label1
If Not xDependRg Is Nothing Then
Set xDependRg = Intersect(xDependRg, Range("C:C"))
End If
Label1:
Set xRg = Intersect(Target, Range("C:C"))
If (Not xRg Is Nothing) And (Not xDependRg Is Nothing) Then
Set xChangeRg = Union(xRg, xDependRg)
ElseIf (xRg Is Nothing) And (Not xDependRg Is Nothing) Then
Set xChangeRg = xDependRg
ElseIf (Not xRg Is Nothing) And (xDependRg Is Nothing) Then
Set xChangeRg = xRg
Else
Application.EnableEvents = True
Exit Sub
End If
xDic.RemoveAll
For I = 1 To xChangeRg.Areas.Count
Set xRgArea = xChangeRg.Areas(I)
For J = 1 To xRgArea.Count
xDic.Add xRgArea(J).Address, xRgArea(J).Formula
Next
Next
Set xChangeRg = Nothing
Set xRg = Nothing
Set xDependRg = Nothing
Application.EnableEvents = True
End Sub
Чтобы сохранить предыдущее значение ячейки в комментарии, примените приведенный ниже код VBA.
Код VBA: сохранить предыдущее значение ячейки в комментарии
Dim xRg As Range
Dim xChangeRg As Range
Dim xDependRg As Range
Dim xDic As New Dictionary
Private Sub Worksheet_Change(ByVal Target As Range)
Dim I As Long
Dim xCell As Range
Dim xHeader As String
Dim xCommText As String
On Error Resume Next
Application.ScreenUpdating = False
Application.EnableEvents = False
xHeader = "Previous value :"
For I = 0 To UBound(xDic.Keys)
Set xCell = Range(xDic.Keys(I))
If Not xCell.Comment Is Nothing Then xCell.Comment.Delete
With xCell
.AddComment
.Comment.Visible = False
.Comment.Text xHeader & vbCrLf & xDic.Items(I)
End With
Next
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim I, J As Long
Dim xRgArea As Range
On Error GoTo Label1
If Target.Count > 1 Then Exit Sub
Application.EnableEvents = False
Set xDependRg = Target.Dependents
If xDependRg Is Nothing Then GoTo Label1
If Not xDependRg Is Nothing Then
Set xDependRg = Intersect(xDependRg, Range("C:C"))
End If
Label1:
Set xRg = Intersect(Target, Range("C:C"))
If (Not xRg Is Nothing) And (Not xDependRg Is Nothing) Then
Set xChangeRg = Union(xRg, xDependRg)
ElseIf (xRg Is Nothing) And (Not xDependRg Is Nothing) Then
Set xChangeRg = xDependRg
ElseIf (Not xRg Is Nothing) And (xDependRg Is Nothing) Then
Set xChangeRg = xRg
Else
Application.EnableEvents = True
Exit Sub
End If
xDic.RemoveAll
For I = 1 To xChangeRg.Areas.Count
Set xRgArea = xChangeRg.Areas(I)
For J = 1 To xRgArea.Count
xDic.Add xRgArea(J).Address, xRgArea(J).Text
Next
Next
Set xChangeRg = Nothing
Set xRg = Nothing
Set xDependRg = Nothing
Application.EnableEvents = True
End Sub
Внимание: В коде цифра 7 указывает столбец G, в который вы сохраните предыдущую ячейку, а C: C — столбец, в котором вы сохраните предыдущее значение ячейки. Пожалуйста, измените их в соответствии с вашими потребностями.
3. Нажмите Tools
> Рекомендации для открытия Ссылки — VBAProject диалоговое окно, проверьте Среда выполнения сценариев Microsoft поле и, наконец, щелкните OK кнопка. Смотрите скриншот:
4. нажмите другой + Q ключи, чтобы закрыть Microsoft Visual Basic для приложений окно.
С этого момента, когда значение ячейки в столбце C обновляется, предыдущее значение ячейки будет сохранено в соответствующие ячейки в столбце G или будет сохранено в комментариях, как показано на скриншотах ниже.
Сохраните предыдущие значения ячеек в других ячейках:
Сохраните предыдущие значения ячеек в комментариях:
Лучшие инструменты для работы в офисе
Kutools for Excel Решит большинство ваших проблем и повысит вашу производительность на 80%
- Снова использовать: Быстро вставить сложные формулы, диаграммы и все, что вы использовали раньше; Зашифровать ячейки с паролем; Создать список рассылки и отправлять электронные письма …
- Бар Супер Формулы (легко редактировать несколько строк текста и формул); Макет для чтения (легко читать и редактировать большое количество ячеек); Вставить в отфильтрованный диапазон…
- Объединить ячейки / строки / столбцы без потери данных; Разделить содержимое ячеек; Объединить повторяющиеся строки / столбцы… Предотвращение дублирования ячеек; Сравнить диапазоны…
- Выберите Дубликат или Уникальный Ряды; Выбрать пустые строки (все ячейки пустые); Супер находка и нечеткая находка во многих рабочих тетрадях; Случайный выбор …
- Точная копия Несколько ячеек без изменения ссылки на формулу; Автоматическое создание ссылок на несколько листов; Вставить пули, Флажки и многое другое …
- Извлечь текст, Добавить текст, Удалить по позиции, Удалить пробел; Создание и печать промежуточных итогов по страницам; Преобразование содержимого ячеек в комментарии…
- Суперфильтр (сохранять и применять схемы фильтров к другим листам); Расширенная сортировка по месяцам / неделям / дням, периодичности и др .; Специальный фильтр жирным, курсивом …
- Комбинируйте книги и рабочие листы; Объединить таблицы на основе ключевых столбцов; Разделить данные на несколько листов; Пакетное преобразование xls, xlsx и PDF…
- Более 300 мощных функций. Поддерживает Office/Excel 2007-2021 и 365. Поддерживает все языки. Простое развертывание на вашем предприятии или в организации. Полнофункциональная 30-дневная бесплатная пробная версия. 60-дневная гарантия возврата денег.
Вкладка Office: интерфейс с вкладками в Office и упрощение работы
- Включение редактирования и чтения с вкладками в Word, Excel, PowerPoint, Издатель, доступ, Visio и проект.
- Открывайте и создавайте несколько документов на новых вкладках одного окна, а не в новых окнах.
- Повышает вашу продуктивность на 50% и сокращает количество щелчков мышью на сотни каждый день!
Комментарии (20)
Оценок пока нет. Оцените первым!
Мне понадобилась эта функция, и мне не нравились все вышеприведенные решения после большинства попыток, поскольку они либо
- Медленный
- Имеют сложные последствия, например, применение application.undo.
- Не снимать, если они не были выбраны.
- Не фиксирует значения, если они не были изменены до
- Слишком сложно
Ну, я очень об этом думал, и я закончил решение для полной истории UNDO, REDO.
Чтобы записать старое значение, на самом деле это очень просто и очень быстро.
Мое решение состоит в том, чтобы захватить все значения, как только пользователь откроет лист, откроется в переменную и будет обновляться после каждого изменения. эта переменная будет использоваться для проверки старого значения ячейки. В решениях, прежде всего, они используются для цикла. На самом деле есть более простой способ.
Чтобы записать все значения, я использовал эту простую команду
SheetStore = sh.UsedRange.Formula
Да, просто это, На самом деле excel вернет массив, если диапазон — это несколько ячеек, поэтому нам не нужно использовать команду FOR EACH, и это очень быстро
Следующий подраздел — это полный код, который должен вызываться в Workbook_SheetActivate. Для сбора изменений необходимо создать другой элемент. Например, у меня есть sub под названием «catchChanges», который работает на Workbook_SheetChange. Он зафиксирует изменения, а затем сохранит их на другом листе истории изменений. затем запускает 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
теперь, чтобы получить старое значение, это очень просто, так как массив имеет одинаковый адрес ячеек
если мы хотим использовать ячейку 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)
это фрагмент, объясняющий метод, я надеюсь, что всем это нравится
Hello,
I have a column (A) with eight different text drop down options. I need a code that will allow me to track each time the drop down is changed to a different option. I want each change to be tracked in a different cell so that I can see how each line has progressed — e.g. from Received to Query to Invalid etc.
I have the following code which works to remember the last value of the cell from column A, but I cannot figure out how to make it work for my needs:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim I As Long
Dim xCell As Range
Dim xDCell As Range
Dim xHeader As String
Dim xCommText As String
On Error Resume Next
Application.ScreenUpdating = False
Application.EnableEvents = False
xHeader = «Previous value :»
x = xDic.Keys
For I = 0 To UBound(xDic.Keys)
Set xCell = Range(xDic.Keys(I))
Set xDCell = Cells(xCell.Row, 49)
xDCell.Value = «»
xDCell.Value = xDic.Items(I)
Next
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim I, J As Long
Dim xRgArea As Range
On Error GoTo Label1
If Target.Count > 1 Then Exit Sub
Application.EnableEvents = False
Set xDependRg = Target.Dependents
If xDependRg Is Nothing Then GoTo Label1
If Not xDependRg Is Nothing Then
Set xDependRg = Intersect(xDependRg, Range(«A:A»))
End If
Label1:
Set xRg = Intersect(Target, Range(«A:A»))
If (Not xRg Is Nothing) And (Not xDependRg Is Nothing) Then
Set xChangeRg = Union(xRg, xDependRg)
ElseIf (xRg Is Nothing) And (Not xDependRg Is Nothing) Then
Set xChangeRg = xDependRg
ElseIf (Not xRg Is Nothing) And (xDependRg Is Nothing) Then
Set xChangeRg = xRg
Else
Application.EnableEvents = True
Exit Sub
End If
xDic.RemoveAll
For I = 1 To xChangeRg.Areas.Count
Set xRgArea = xChangeRg.Areas(I)
For J = 1 To xRgArea.Count
xDic.Add xRgArea(J).Address, xRgArea(J).Formula
Next
Next
Set xChangeRg = Nothing
Set xRg = Nothing
Set xDependRg = Nothing
Application.EnableEvents = True
End Sub
Essentially, I want every change made in column A tracked in column AW, AX, AY and so on. There will be no more than 10 changes to an individual cell in column A.
Thanks.