Макрос для копирования данных из вкладок на основе отфильтрованной строки - PullRequest
0 голосов
/ 02 января 2019

Я хотел бы иметь макрос, который копирует все данные из нескольких вкладок, которые соответствуют строке фильтра, определенной на вкладке «Сводка (отфильтрованные)».Вот подробности:

  1. Все вкладки имеют одинаковые заголовки.
  2. Строка фильтра - строка 7 на вкладке «Сводка (отфильтрованные)».
  3. Я хочу пройтись по каждой вкладке, кроме перечисленных ниже, проверить каждую строку и скопировать ее на вкладку Сводка, если она удовлетворяет фильтру (если данная ячейка в строке фильтра пуста, все значения разрешены, в противном случае этодолжны совпадать).
  4. Я бы хотел, чтобы копирование началось в строке 9 вкладки «Сводка».

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

Sub CopyDataFiltered()
    Dim sh          As Worksheet
    Dim SourceSh    As Worksheet
    Dim Last        As Long
    Dim shLast      As Long
    Dim CopyRng     As Range
    Dim StartRow    As Long
    Dim lrow        As Long
    Dim r           As Long
    Dim col         As Long

    With Application
        .ScreenUpdating = False
        .EnableEvents = False
    End With

    Set SourceSh = ActiveWorkbook.Worksheets("Summary (Filtered)")
    Application.DisplayAlerts = False
    On Error Resume Next
    On Error GoTo 0

    For Each sh In ActiveWorkbook.Worksheets
        If IsError(Application.Match(sh.Name, Array(SourceSh.Name, "List Data", "Summary (All)", "Lists"), 0)) Then

            lrow = LastRow(sh)

            If lrow < 7 Then
                'MsgBox ("Nothing to move")
                GoTo NextTab
            End If

            For r = LastRow(sh) To 7 Step -1
                For col = 1 To 16

                    If SourceSh.Range(7, col).Value <> "" And SourceSh.Range(7, col).Value <> sh.Range(r, col).Value Then
                        GoTo End1
                    End If

                Next col
                sh.Rows(r).Copy Destination:=SourceSh.Range("A" & LastRow(SourceSh) + 1)

End1:
            Next r
        End If
NextTab:
    Next

ExitTheSub:
    Application.Goto SourceSh.Cells(1)
    Application.DisplayAlerts = True
    With Application
        .ScreenUpdating = True
        .EnableEvents = True
    End With
End Sub

Не могли бы вы взглянуть и сообщить, что, по вашему мнению, будет лучшим?

1 Ответ

0 голосов
/ 02 января 2019

Итак, это почти тот же самый подход, просто переработанный в форму, которая изолирует каждый шаг вашего процесса, уточняя, чего вы хотите достичь.Наличие вложенного цикла не является проблемой, если вы отслеживаете то, что пытаетесь сделать.То, от чего я хочу уклониться, - это использование GoTo утверждений.Они почти никогда не нужны.

Итак, обо всем по порядку ...

Всегда используйте Option Explicit и объявляйте свои переменные как можно ближе к тому месту, где вы хотите их использовать.Эта привычка облегчает понимание того, что представляет собой каждая переменная и для чего она используется.Если вы объявляете их все в верхней части, вы всегда будете возвращаться туда и обратно, чтобы найти их.

Option Explicit

Sub CopyFilteredData()
    Dim srcWB As Workbook
    Dim srcWS As Worksheet
    Set srcWB = ActiveWorkbook
    Set srcWS = srcWB.Sheets("Summary (Filtered)")

Поскольку вы всегда будете ссылаться на свой фильтр в одном и том же месте, просто определите переменную, котораяспециально соответствует вашему фильтру.Бонус здесь заключается в том, что если ваш фильтр меняется со строки 7 на строку 8 (например), вам нужно изменить его только в одном месте.

    Dim srcFilter As Range
    Set srcFilter = srcWS.Range("A7").Resize(1, 16)

Используя ту же идею, установите переменную, которая четко определяетрабочие листы, которые нужно пропустить:

    Dim skipTheseSheets As Variant
    skipTheseSheets = Array(srcWS.Name, "List Data", "Summary (All)", "Lists")
    Dim sh As Worksheet
    For Each sh In srcWB.Sheets
        If Not IsInArray(sh.Name, skipTheseSheets) Then

Этот ответ дает отличную функцию, чтобы проверить, существует ли имя вашего рабочего листа в этом массиве.

Вы не включили свою функциюдля LastRow, поэтому я включил его в свой ответ.Тем не менее, сделайте привычку называть свои функции, используя глагол, который описывает, что делает функция.В этом случае FindLastRow.

Чтобы прекратить использование операторов GoTo, просто измените оператор If и продолжите, если он пройдет:

Dim lastRow As Long
lastRow = FindLastRow(sh)
If lastRow > 7 Then

Я создал отдельную функциюкоторый сравнивает данную строку с вашим фильтром.Он использует в основном ту же логику, но, изолируя ее как функцию, он делает вашу основную логику более простой для чтения.Кроме того, обратите внимание, что вы можете выйти из цикла For и избежать страшных GoTo:

Private Function RowMatchesFilter(ByRef thisRow As Range, _
                                  ByRef thisFilter As Range) As Boolean
    '--- the row matches only if the value in thisRow equals the value
    '    in the filter
    RowMatchesFilter = True
    Dim i As Long
    For i = 1 To 16
        If Not IsEmpty(thisFilter.Cells(1, i).Value) Then
            If thisRow.Cells(1, i).Value <> thisFilter.Cells(1, i).Value Then
                '--- the first cell that doesn't match invalidates the
                '    entire row
                RowMatchesFilter = False
                Exit For
            End If
        End If
    Next i
End Function

Таким образом, ваш цикл копирования в конечном итоге будет выглядеть так:

Dim r As Long
For r = lastRow To 7 Step -1
    If RowMatchesFilter(sh.Rows(r), srcFilter) Then
        sh.Rows(r).Copy
        srcWS.Range("A" & FindLastRow(srcWS) + 1).PasteSpecial xlPasteAll
    End If
Next r

ВотВесь модуль:

Option Explicit

Sub CopyFilteredData()
    Dim srcWB As Workbook
    Dim srcWS As Worksheet
    Set srcWB = ActiveWorkbook
    Set srcWS = srcWB.Sheets("Summary (Filtered)")

    Dim srcFilter As Range
    Set srcFilter = srcWS.Range("A7").Resize(1, 16)

    Dim skipTheseSheets As Variant
    skipTheseSheets = Array(srcWS.Name, "List Data", "Summary (All)", "Lists")

    Dim sh As Worksheet
    For Each sh In srcWB.Sheets
        If Not IsInArray(sh.Name, skipTheseSheets) Then
            Dim lastRow As Long
            lastRow = FindLastRow(sh)
            If lastRow > 7 Then
                '--- now copy the data from this sheet back to the source
                '    in reverse order, using the source filter line to
                '    direct which cells to copy
                Dim r As Long
                For r = lastRow To 7 Step -1
                    If RowMatchesFilter(sh.Rows(r), srcFilter) Then
                        sh.Rows(r).Copy
                        srcWS.Range("A" & FindLastRow(srcWS) + 1).PasteSpecial xlPasteAll
                    End If
                Next r
            End If
        End If
    Next sh
End Sub

Private Function IsInArray(ByVal stringToBeFound As String, _
                           ByRef arr As Variant) As Boolean
  IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function

Private Function FindLastRow(ByRef thisWS As Worksheet) As Long
    With thisWS
        FindLastRow = .Cells(.Cells.Rows.Count, 1).End(xlUp).Row
    End With
End Function

Private Function RowMatchesFilter(ByRef thisRow As Range, _
                                  ByRef thisFilter As Range) As Boolean
    '--- the row matches only if the value in thisRow equals the value
    '    in the filter
    RowMatchesFilter = True
    Dim i As Long
    For i = 1 To 16
        If Not IsEmpty(thisFilter.Cells(1, i).Value) Then
            If thisRow.Cells(1, i).Value <> thisFilter.Cells(1, i).Value Then
                '--- the first cell that doesn't match invalidates the
                '    entire row
                RowMatchesFilter = False
                Exit For
            End If
        End If
    Next i
End Function
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...