Отслеживание изменений на листе, копирование активной ячейки, изменение заголовка столбца и NewColumnValue

#excel #vba #events #target #worksheet-function

#excel #vba #Мероприятия #цель #рабочий лист-функция

Вопрос:

ССЫЛКА: Отслеживание изменений на листе, копирование ячейки, которая не является активной ячейкой в строке активной ячейки, и запись значения

Я обновил свой рабочий лист и надеюсь получить представление о том, как получать значения в ячейках при изменении события…

  • NewColumn__Value — в настоящее время ни один из них не извлекает правильное значение. активная ячейка влияет на результаты и требует изменения до и после события, чтобы сравнить позже. я знаю, что в настоящее время он выводит то же самое, что и соответствующий OldVColumn_Value, но оставлен, чтобы помочь с передачей ask.
  • ColumnHeader — в настоящее время не извлекает никакого значения. заголовок находится во 2-й строке ‘target.value

любые мысли по консолидации кода приветствуются. я вынужден использовать подход «длинного деления», и так здорово видеть, как перейти к подходу «калькулятор», помогает мне учиться.

 Option Explicit
Public OldValue, OldColumnJValue, ColumnHeaderX, ColumnJValue, ColumnHeader, OldColumnJJValue, 
OldColumnJKValue, OldColumnJLValue, OldColumnJMValue, NewColumnJJValue, NewColumnJKValue, 
NewColumnJLValue, NewColumnJMValue, OldColumnMPValue, OldColumnMQValue, OldColumnMRValue, 
OldColumnMSValue, NewColumnMPValue, NewColumnMQValue, NewColumnMRValue, NewColumnMSValue, 
OldColumnPVValue, OldColumnPWValue, OldColumnPXValue, OldColumnPYValue, NewColumnPVValue, 
NewColumnPWValue, NewColumnPXValue, NewColumnPYValue

Private Sub Worksheet_Change(ByVal Target As Range)
    Application.EnableEvents = False
        With Sheets("LogDetails").Range("A" amp; Rows.Count).End(xlUp)
            .Offset(1, 0) = ActiveSheet.Name
            .Offset(1, 1) = Target.Address(0, 0)
            .Offset(1, 2) = Environ("username")
            .Offset(1, 3) = Now
                'add empl name vlookup formula to this column?
            .Offset(1, 5) = ColumnJValue
            **.Offset(1, 6) = ColumnHeader**
            .Offset(1, 7) = OldValue
            .Offset(1, 8) = Target
                '2020 pre-change value below
            .Offset(1, 9) = OldColumnJJValue
            .Offset(1, 10) = OldColumnJKValue
            .Offset(1, 11) = OldColumnJLValue
            .Offset(1, 12) = OldColumnJMValue
                '2020 post-change value below
            **.Offset(1, 13) = NewColumnJJValue
            .Offset(1, 14) = NewColumnJKValue
            .Offset(1, 15) = NewColumnJLValue
            .Offset(1, 16) = NewColumnJMValue**
                '2021 pre-change value below
            .Offset(1, 18) = OldColumnMPValue
            .Offset(1, 19) = OldColumnMQValue
            .Offset(1, 20) = OldColumnMRValue
            .Offset(1, 21) = OldColumnMSValue
                '2021 post-change value below
            **.Offset(1, 22) = NewColumnMPValue
            .Offset(1, 23) = NewColumnMQValue
            .Offset(1, 24) = NewColumnMRValue
            .Offset(1, 25) = NewColumnMSValue**
                '2022 pre-change value below
            .Offset(1, 27) = OldColumnPVValue
            .Offset(1, 28) = OldColumnPWValue
            .Offset(1, 29) = OldColumnPXValue
            .Offset(1, 30) = OldColumnPYValue
                '2022 post-change value below
            **.Offset(1, 31) = NewColumnPVValue
            .Offset(1, 32) = NewColumnPWValue
            .Offset(1, 33) = NewColumnPXValue
            .Offset(1, 34) = NewColumnPYValue**
        End With
    Application.EnableEvents = True
End Sub


Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    If Selection.Cells.Count = 1 Then
        OldValue = Target
           'Program name changed
        ColumnJValue = Range("A1")(Target.Row, 10)
           'Column header of changed cell
        **ColumnHeader = Range("A1")(Target.Row, (2, 0)**
           '2020 pre-change value below
        OldColumnJJValue = Range("A1")(Target.Row, 270)
        OldColumnJKValue = Range("A1")(Target.Row, 271)
        OldColumnJLValue = Range("A1")(Target.Row, 272)
        OldColumnJMValue = Range("A1")(Target.Row, 273)
           '2020 post-change value below
        **NewColumnJJValue = Range("A1")(Target.Row, 270)
        NewColumnJKValue = Range("A1")(Target.Row, 271)
        NewColumnJLValue = Range("A1")(Target.Row, 272)
        NewColumnJMValue = Range("A1")(Target.Row, 273)**
           '2021 pre-change value below
        OldColumnMPValue = Range("A1")(Target.Row, 354)
        OldColumnMQValue = Range("A1")(Target.Row, 355)
        OldColumnMRValue = Range("A1")(Target.Row, 356)
        OldColumnMSValue = Range("A1")(Target.Row, 357)
           '2021 post-change value below
        **NewColumnMPValue = Range("A1")(Target.Row, 354)
        NewColumnMQValue = Range("A1")(Target.Row, 355)
        NewColumnMRValue = Range("A1")(Target.Row, 356)
        NewColumnMSValue = Range("A1")(Target.Row, 367)**
           '2022 pre-change value below
        OldColumnPVValue = Range("A1")(Target.Row, 438)
        OldColumnPWValue = Range("A1")(Target.Row, 439)
        OldColumnPXValue = Range("A1")(Target.Row, 440)
        OldColumnPYValue = Range("A1")(Target.Row, 441)
           '2022 post-change value below
        **NewColumnPVValue = Range("A1")(Target.Row, 438)
        NewColumnPWValue = Range("A1")(Target.Row, 439)
        NewColumnPXValue = Range("A1")(Target.Row, 440)
        NewColumnPYValue = Range("A1")(Target.Row, 441)**
        Exit Sub
    End If
    MsgBox "Multiple cell selections are not allowed on this sheet", vbCritical
    ActiveCell.Select
End Sub
 

Комментарии:

1. Я не уверен, в чем заключается вопрос, но я бы, вероятно, попытался использовать словарь вместо длинного списка переменных и перебирать его там, где это возможно.

2. В основном спрашиваю о 2 вещах.. Во-первых, когда изменяется активная ячейка, скажем, R13, это влияет на значения других ячеек в этой строке. Я хочу скопировать эти значения и поместить в logdetails ws. Все хорошо с получением старых значений до изменения из Col JJ13: JM13, MP13: MS13 и PV13: PY13 (и т.д.), Но также необходимо записать значения после изменения тех же ячеек. Изменение в R13 влияет на эти ячейки, и я хочу записать значения до / после изменения. Затем выведите данные на вкладку logdetails с другими значениями. Во-вторых, там, где происходит активное изменение ячейки, запишите заголовок столбца, расположенный в строке 2

3. Если каждое изменение записывается по своему новому значению, то нет необходимости записывать и предыдущее значение в это время, поскольку оно уже есть в журнале, записанном при последнем изменении.

4. мне кажется, я понимаю вас. да, я пытался использовать эти значения при изменении следующей ячейки. я не могу получить значения, помещенные в правильную строку, где они должны быть для последующего анализа. его нужно сдвинуть вверх на 1 ряд. он хочет поместить все данные в logdetails в одной строке, независимо от того, на какое смещение строки я их меняю.

5. я смог использовать старые значения в другом подходе, так что я хорош. спасибо за все идеи.

Ответ №1:

Я рад узнать из вашего комментария, что вы решили свою проблему. Молодец! Я действительно думал, что вы справились с задачей довольно героически, и не хочу ни выбрасывать мою работу, ни лишать вас ее возможных преимуществ. Пожалуйста, обратите внимание.

 Option Explicit

Private PrevVal(1)  As Variant      ' previously selected row data
                                    ' PrevVal(0) = row number, PrevVal(1) = row's data
Enum Nws                            ' data tab (ActiveSheet)
    ' 147
    NwsHeaderRow = 2                ' change to suit (data start immediately below this row)
    NwsClmJ = 10                    ' Debug.Print Columns("J").Column
    NwsClmJJ = 270
    NwsClmJK                        ' no assigned value means preceding   1
    NwsClmJL
    NwsClmJM
    NwsClmMP = 354
    NwsClmMQ
    NwsClmMR
    NwsClmMS
    NwsClmPV = 438
    NwsClmPW
    NwsClmPX
    NwsClmPY
    NwsTop                      ' defining the last used column
End Enum

Private Sub Worksheet_Activate()
    ' 147
    SetPrevVal ActiveCell.Row
End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    ' 147
    SetPrevVal Target.Row
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)
    ' 147
    
    Dim TriggerRange    As Range        ' range of relevant changes
    Dim MsgTxt()        As String       ' error message
    Dim Log(1 To 34)    As Variant      ' Log entry
    Dim Employee        As String       ' employee's name (retrieved by VLOOKUP)
    Dim i               As Long         ' index of Log()
    
    Set TriggerRange = Range(Cells(NwsHeaderRow   1, 1), _
                             Cells(Rows.Count, "A").End(xlUp)) _
                             .Resize(, NwsTop - 1)
                             Debug.Print TriggerRange.Address
    With Target
        If Not Application.Intersect(Target, TriggerRange) Is Nothing Then
            If .Cells.CountLarge > 1 Then
                MsgTxt = Split("Please change only one cell at a time on this sheet." amp; _
                             "|Unsupported user action", "|")
            Else
                If IsEmpty(PrevVal) Then
                    MsgTxt = Split("?")
                ElseIf PrevVal(0) <> .Row Then
                    MsgTxt = Split("?")
                End If
                If Join(MsgTxt) = "?" Then
                    MsgTxt = Split("Sorry, I lost the previous record." amp; vbCr amp; _
                                   "Please repeat the action." amp; _
                                 "|Internal error", "|")
                End If
            End If
            If Len(Join(MsgTxt)) Then
                MsgBox MsgTxt(0), vbCritical, MsgTxt(1)
                Application.Undo
                .Select
                Exit Sub
            End If
            
            Employee = ""        ' add empl name vlookup formula here
            For i = 1 To 8
                Log(i) = Array(Environ("username"), Employee, Now, _
                               ActiveSheet.Name, Cells(.Row, NwsClmJ).Value, _
                               .Address(0, 0), PrevVal(1)(1, .Column), _
                               .Value)(i - 1)
            Next i
            
            For i = 9 To 12
                Log(i) = PrevVal(1)(1, NwsClmJJ   i - 9)
                Log(i   4) = Cells(.Row, NwsClmJJ   i - 9).Value
            Next i
            ' column 17 remains blank by your design
            
            For i = 18 To 21
                Log(i) = PrevVal(1)(1, NwsClmMP   i - 18)
                Log(i   4) = Cells(.Row, NwsClmMP   i - 18).Value
            Next i
            ' column 26 remains blank by your design
            
            For i = 27 To 30
                Log(i) = PrevVal(1)(1, NwsClmPV   i - 27)
                Log(i   4) = Cells(.Row, NwsClmPV   i - 27).Value
            Next i
        
            With Application
                .EnableEvents = False
                .ScreenUpdating = False
            End With
            
            With Worksheets("Log")
                .Cells(.Rows.Count, "A").End(xlUp).Offset(1) _
                       .Resize(1, UBound(Log)).Value = Log
            End With
                
            With Application
                .EnableEvents = True
                .ScreenUpdating = True
            End With
        End If
    End With
End Sub

Private Function SetPrevVal(ByVal R As Long) As Range
    ' 147
    
    Dim Rl      As Long         ' last used row in column [270]
    
    ' presuming that column 1 offers a relevant measurement
    Rl = Cells(Rows.Count, 1).End(xlUp).Row
    
    ' don't record if the selection is in or above the caption row (NwsHeaderRow)
    ' or below the data range as defined by the end of 'FirstCl'
    ' you might add an exception for columns < `NwsClmJJ` ??
    If (R > NwsHeaderRow) And (R <= Rl) Then
        PrevVal(0) = R
        ' presuming that there is a header for every column in Header Row
        PrevVal(1) = DataRange(R).Value
    End If
End Function

Private Function DataRange(ByVal R As Long) As Range
    ' 147
    
    ' presuming that there is a header for every column in the Header Row
    Set DataRange = Range(Cells(R, 1), Cells(NwsHeaderRow, Columns.Count).End(xlToLeft) _
                          .Offset(R - NwsHeaderRow))
End Function
 

Из-за нехватки данных я провел очень ограниченное тестирование этого кода: он создает запись в журнале, в основном в соответствии с тем, что, по-видимому, предлагает ваш код. Как только вы освоитесь, он будет таким же прозрачным, как и ваш собственный, что дает мне надежду, что вы сможете следовать ему и, например, вставить отсутствующее имя сотрудника. Добавить дату или столбцы очень легко, и это намного быстрее, чем то, что у вас было. Также есть немало комментариев, которые должны оказаться полезными.

Одно слово о «старых» значениях. Я заменил ваши многочисленные общедоступные переменные одним массивом, который записывает все данные в одну строку. Это происходит при загрузке (активации) листа и после этого при каждом нажатии. Если по какой-либо причине (что чаще всего происходит во время тестирования) массив недоступен или доступен неправильный массив, пользователю будет предложено повторить его изменение. Это довольно водонепроницаемо.

Комментарии:

1. вау, спасибо …! я обязательно разберусь с этим. стоит учиться!

2. это, безусловно, более быстрый подход к протоколированию данных. я ввел новый документ Excel с данными, программа работала почти без проблем. мне пришлось удалить часть аргумента if counterlarge. это работало при множественном выборе пустой ячейки, но когда вы выполняли с фактическими несколькими ячейками данных, это приводило к ошибкам. другой вопрос, отображается ли NwsHeaderRow? я не могу найти вывод ни на ws. спасибо за любую дополнительную информацию.

3. Я изменил свой код, чтобы использовать Target.Cells.CountLarge вместо Target.CountLarge . Ни одно из перечислений не отображается на рабочем листе. Они служат для присвоения имен числам, и эти имена используются во всем коде вместо жестких чисел. Например, NwsHeaderRow = 2 , потому что ваша строка заголовка находится в строке листа 2. Если вы хотите переместить его в строку 1 или 3, вы можете просто изменить значение, присвоенное перечислению NwsHeaderRow, и остальная часть кода автоматически адаптируется к изменению.

4. Кстати, я полагаю, что не все имена, приведенные в Перечислении, на самом деле были окончательно использованы в коде. Это легко исправить, если это вас беспокоит. Я оставил его нетронутым, потому что он показывает логику, реализованную в Перечислении.

5. мне удалось заставить встречные аргументы (обмен сообщениями) работать должным образом. далее я собираюсь посмотреть на расположение отображения NwsHeaderRow. мне нравится направление, в котором этот код ведет меня, любые дальнейшие идеи будут приветствоваться.