Определите, добавляет ли пользователь или удаляет строки - PullRequest
20 голосов
/ 20 сентября 2011

У меня есть макрос VBA, который проверяет введенные пользователем данные (я специально не использовал проверку данных / условное форматирование).

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

Есть ли в любом случае, чтобы различать эти два?

Ответы [ 7 ]

17 голосов
/ 20 сентября 2011

Вы можете определить имя диапазона, например RowMarker =$A$1000

Тогда этот код в вашем событии изменения сохранит положение этого маркера относительно его предыдущей позиции и сообщит о любом изменении (затем сохранит новую позицию)

Private Sub Worksheet_Change(ByVal Target As Range)
    Static lngRow As Long
    Dim rng1 As Range
    Set rng1 = ThisWorkbook.Names("RowMarker").RefersToRange
    If lngRow = 0 Then
    lngRow = rng1.Row
        Exit Sub
    End If
    If rng1.Row = lngRow Then Exit Sub
    If rng1.Row < lngRow Then
        MsgBox lngRow - rng1.Row & " rows removed"
    Else
        MsgBox rng1.Row - lngRow & " rows added"
    End If
    lngRow = rng1.Row
End Sub
4 голосов
/ 20 сентября 2011

Попробуйте этот код:

Private Sub Worksheet_Change(ByVal Target As Range)
    Dim lNewRowCount As Long

    ActiveSheet.UsedRange
    lNewRowCount = ActiveSheet.UsedRange.Rows.Count

    If lOldRowCount = lNewRowCount Then
    ElseIf lOldRowCount > lNewRowCount Then
        MsgBox ("Row Deleted")
        lOldRowCount = lNewRowCount
    ElseIf lOldRowCount < lNewRowCount Then
        MsgBox ("Row Inserted")
        lOldRowCount = lNewRowCount
    End If

End Sub

Также добавьте это в модуль ThisWorkBook:

Private Sub Workbook_Open()
    ActiveSheet.UsedRange
    lOldRowCount = ActiveSheet.UsedRange.Rows.Count
End Sub

И затем это в своем собственном модуле:

Public lOldRowCount As Long

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

Если вы не хотите использовать переменную Public и событие открытия рабочего листа, тогда вы можете использовать именованный диапазон на рабочем листе и хранить количество строк (lRowCount).) там.

4 голосов
/ 20 сентября 2011

Предположение : «Различать два» означает отличать добавление / удаление строки от любого другого типа изменения. Если вы имели в виду, как определить, было ли изменение добавить строку ИЛИ удалить строку, игнорируйте мой ответ ниже.

В случае вставки или удаления строки, target.cells.count будет все ячейки в строке. Таким образом, вы можете использовать это заявление If, чтобы захватить его. Обратите внимание, что я использую cell.columns.count, поскольку он может отличаться для каждого файла. Это также сработает, если пользователь выберет целую строку и нажмет «удалить» (чтобы стереть значения), так что вам нужно будет обойти это, хотя ...

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

If Target.Cells.Count = Cells.Columns.Count Then
    MsgBox "Row added or deleted"
End If

End Sub
2 голосов
/ 22 июля 2016

После поисков решил немного решить это сам. В модуле «Рабочий лист» (например, «Лист1» в «Объектах Microsoft Excel» в редакторе VBA) введите следующее:

Private usedRowsCount As Long 'use private to limit access to var outside of sheet

'Because select occurs before change we can record the current usable row count
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  usedRowsCount = Target.Worksheet.UsedRange.rows.count 'record current row count for row event detection
End Sub

'once row count recorded at selection we can compare the used row count after change occurs
'with the Target.Address we can also detect which row has been added or removed if you need to do further mods on that row
Private Sub Worksheet_Change(ByVal Target As Range)
  If usedRowsCount < Target.Worksheet.UsedRange.rows.count Then
    Debug.Print "Row Added: ", Target.Address
  ElseIf usedRowsCount > Target.Worksheet.UsedRange.rows.count Then
    Debug.Print "Row deleted: ", Target.Address
  End If
End Sub
1 голос
/ 15 августа 2015

Некоторые из ваших конечных целей по различению вставок и удалений заканчиваются тем, что будет определять, как вы хотите продолжить работу после идентификации вставки или удаления. Следующее может быть существенно сокращено, но я попытался охватить все возможные сценарии.

Private Sub Worksheet_Change(ByVal Target As Range)

    On Error GoTo bm_Safe_Exit
    Application.EnableEvents = False
    Application.ScreenUpdating = False
    Dim olr As Long, nlr As Long, olc As Long, nlc As Long

    With Target.Parent.Cells
        nlc = .Find(what:=Chr(42), after:=.Cells(1), LookIn:=xlValues, lookat:=xlPart, _
                SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
        nlr = .Find(what:=Chr(42), after:=.Cells(1), LookIn:=xlValues, lookat:=xlPart, _
                SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
        Application.Undo    'undo the last change event
        olc = .Find(what:=Chr(42), after:=.Cells(1), LookIn:=xlValues, lookat:=xlPart, _
                SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
        olr = .Find(what:=Chr(42), after:=.Cells(1), LookIn:=xlValues, lookat:=xlPart, _
                SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
        Application.Repeat  'redo the last change event
    End With

    If nlr <> olr Or nlc <> olc Then
        Select Case nlr
            Case olr - 1
                Debug.Print "One (1) row has been deleted"
            Case Is < (olr - 1)
                Debug.Print (olr - nlr) & " rows have been deleted"
            Case olr + 1
                Debug.Print "One (1) row has been inserted"
            Case Is > (olr + 1)
                Debug.Print (nlr - olr) & " rows have been inserted"
            Case olr
                Debug.Print "No rows have been deleted or inserted"
            Case Else
                'don't know what else could happen
        End Select
        Select Case nlc
            Case olc - 1
                Debug.Print "One (1) column has been deleted"
            Case Is < (olc - 1)
                Debug.Print (olc - nlc) & " columns have been deleted"
            Case olc + 1
                Debug.Print "One (1) column has been inserted"
            Case Is > (olc + 1)
                Debug.Print (nlc - olc) & " columns have been inserted"
            Case olc
                Debug.Print "No columns have been deleted or inserted"
            Case Else
                'don't know what else could happen
        End Select
    Else
        'deal with standard Intersect(Target, Range) events here
    End If

bm_Safe_Exit:
    Application.EnableEvents = True
    Application.ScreenUpdating = True

End Sub

По сути, этот код идентифицирует последнюю ячейку по столбцам и последнюю ячейку по строкам. Затем он отменяет последнюю операцию и проверяет снова. Сравнение двух результатов позволяет определить, была ли строка / столбец вставлена ​​/ удалена. После выполнения четырех измерений выполняется повторное выполнение последней операции, чтобы можно было обрабатывать любые другие более стандартные операции Worksheet_Change.

0 голосов
/ 08 апреля 2017

Захватывать добавления и удаления строк в событии worksheet_change.

Я создаю именованный диапазон с именем "CurRowCnt";формула: = ROWS (Таблица1).Доступ в коде VBA с помощью:

CurRowCnt = Evaluate(Application.Names("CurRowCnt").RefersTo)

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

Я сохраняю CurRowCnt в пользовательском свойстве документа, опять же в целях стабильности.

ThisWorkbook.CustomDocumentProperties("RowCnt").Value = Evaluate(Application.Names("CurRowCnt").RefersTo)

My Worksheet_Change Структура события выглядит следующим образом:

Dim CurRowCnt as Double
CurRowCnt = Evaluate(Application.Names("CurRowCnt").RefersTo)
Select Case CurRowCnt

    '' ########## ROW(S) ADDED
     Case Is > ThisWorkbook.CustomDocumentProperties("RowCnt").Value
         Dim r As Range
         Dim NewRow as Range

         ThisWorkbook.CustomDocumentProperties("RowCnt").Value = _
         Evaluate(Application.Names("CurRowCnt").RefersTo)

         For Each r In Selection.Rows.EntireRow
             Set NewRow = Intersect(Application.Range("Table1"), r)
             'Process new row(s) here
         next r

    '' ########## ROW(S) DELETED
     Case Is < ThisWorkbook.CustomDocumentProperties("RowCnt").Value

         ThisWorkbook.CustomDocumentProperties("RowCnt").Value = _
         Evaluate(Application.Names("CurRowCnt").RefersTo)

         'Process here

    '' ########## CELL CHANGE
    'Case Is = RowCnt

        'Process here

    '' ########## PROCESSING ERROR
    Case Else 'Should happen only on error with CurRowCnt or RowCnt
        'Error msg here

End Select
0 голосов
/ 04 ноября 2014

Существует два подхода, основанных на следующем шаблоне.

  1. Определить модуль или переменную модуля класса типа Range.
  2. «Зафиксируйте» специальный диапазон, присвоив его переменной, используя абсолютный адрес , и сохраните его адрес или размер (зависит от подхода).
  3. Чтобы определить подтип действия пользователя при работе с переменной в обработчике события смены листа.

В первом подходе весь интересующий диапазон присваивается переменной, а размер диапазона сохраняется. Затем в обработчике события смены листа должны быть обработаны следующие случаи:

  • исключение возникает при доступе к Address свойству => закрепленный диапазон больше не существует;
  • адрес измененной ячейки ниже, чем закрепленный диапазон => вставка была => обновить переменную
  • a новый размер закрепленного диапазона отличается от сохраненного (меньше => что-то было удалено, больше => что-то было вставлено).

Во втором подходе диапазон «маркер» назначается переменной (см. Пример ниже), а адрес диапазона сохраняется для определения перемещений или сдвигов в любом направлении . Затем в обработчике события смены листа должны обрабатываться следующие случаи: *

  • исключение возникает при доступе к Address свойству => закрепленный диапазон маркера больше не существует;
  • адрес измененной ячейки ниже, чем диапазон маркера => вставка была => обновить переменную
  • есть разница в любом направлении, то есть abs(new_row - saved_row) > 0 or abs(new_col-saved_col) > 0 => закрепленный диапазон был перемещен или смещен.

Плюсы:

  • Пользовательское имя не используется
  • UsedRange свойство не используется
  • Закрепленный диапазон обновляется в соответствии с действиями пользователя вместо предположения, что действие пользователя не произойдет ниже 1000-й строки.

Минусы:

  • Переменная должна быть назначена в обработчике события открытия рабочей книги, чтобы использовать ее в обработчике изменения листа.
  • Переменная и WithEvents переменная объекта должны быть присвоены Nothing в обработчике события закрытия рабочей книги, чтобы отписаться от события.
  • Невозможно определить операции сортировки, поскольку они изменяют значение диапазона вместо строк обмена.

Следующий пример показывает, что оба подхода могут работать. Определите в модуле:

Private m_st As Range
Sub set_m_st()
  Set m_st = [$A$10:$F$10]
End Sub
Sub get_m_st()
  MsgBox m_st.Address
End Sub

Затем запустите set_m_st (просто поместите курсор в подпрограмму и вызовите действие Run), чтобы определить диапазон $A$10:$F$10. Вставьте или удалите строку или ячейку над ней (не путайте с изменением значения ячейки). Запустите get_m_st, чтобы увидеть измененный адрес закрепленного диапазона. Удалите закрепленный диапазон, чтобы получить исключение «Требуется объект» в get_m_st.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...