Извлечь подмассив из зубчатого массива и использовать в качестве 1d массива - PullRequest
1 голос
/ 24 апреля 2020

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

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

Ниже мой последний Попытка, первая проблема в строке printOut, я получаю Несоответствие типов.

Sub CopyOut()
Dim printOut, groupNames, Group1, groupArray() As Variant
Dim n, j As Long
Dim reNamed, fileName As String
Dim ws As Worksheet
Dim wb1, wb2 As Workbook
groupNames = Array("Group 1", "Group 2", "Group 3", "Group 4") 'other arrays left off for length
Group1 = Array("FA_1A Report", "FA_1A", "FA_2ACS Report", "FA_2ACS", "FA_2BCS Report", "FA_2BCS", "FANUCMED Report", "FANUCMED", "FA_RRTP1 Report", "FA_RRPT1")
groupArray = Array(groupNames, Group1)

For n = 1 To UBound(groupArray)
    fileName = "CS Data Sheet" & " " & Format(Date, "mmmyy") & "-" & groupArray(n - n)(n - 1) & ".xlsm" 'concat file name string. this is not just tacked on the end of reName because i use it on it's own later
    reNamed = "C:\Users\xx\Desktop\" & fileName 'concat save location string
    Set wb1 = ThisWorkbook
    Set wb2 = Workbooks.Add 'create a new workbook, wb2
    wb2.SaveAs fileName:=reNamed, FileFormat:=xlOpenXMLWorkbookMacroEnabled  'save with that name and location
    printOut = Join(Application.Index(groupArray, n, 0), ",")
    wb1.Sheets(printOut).Copy Before:=Workbooks(fileName).Sheets(1) 'copy the sheets for the group and paste into the newly created document
Next
End Sub

Если я вообще откажусь от printOut и введу конкретное c имя рабочего листа, оно будет работать только для этого одного листа (конечно), но мне нужно скопировать несколько в каждый новый документ.

Я также пытался:

For n = 1 To UBound(groupArray)
...
for j= LBound(groupArray(n)) To UBound(groupArray(n))
wb1.Sheets(groupArray(n)(j)).Copy Before:=Workbooks(fileName).Sheets(1)
next
next

перебирать подмассив и копировать лист за раз, но он дает индекс вне диапазона. В этой версии я попробовал различные методы преобразования значения groupArray (n) (j) в строку или в тип «рабочий лист», чтобы установить его в качестве переменной и использовать переменную в sheet (). Copy, но безрезультатно.

Есть идеи, где я могу пойти не так? Большое спасибо

РЕДАКТИРОВАТЬ: мой приведенный выше код работал, заключив его в split (пытался использовать printOut в качестве массива, когда он был только строкой) и исправив аргументы Index, как показано ниже, однако в результате код все еще нуждается в работе, так как, если лист отсутствует, он не будет работать.

printOut = Split(Join(Application.Index(groupArray(n), 1, 0), ","), ",")

Ответы [ 2 ]

0 голосов
/ 25 апреля 2020

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

Я думаю, что подход, при котором вы сохраняете файл «config» в своей главной рабочей книге для настройки групп и листов, более подходит, чем редактирование кода Пример:

enter image description here

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

Option Explicit

Sub CopyOut()
    Dim groupArr() As Variant
    Dim wb2 As Workbook
    Dim lastRow As Long, lastCol As Long, highestNumOfSheets As Long, i As Long, j As Long, arrColumns As Long
    Dim reNamed As String, fileName As String, configSheet As String
    Dim removedSheet1 As Boolean

    ' Modify the sheet name here
    configSheet = "config"

    ' Build an array from sheet defined groups
    With ThisWorkbook.Worksheets(configSheet)
        lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row

        For i = 2 To lastRow
            lastCol = .Cells(i, .Columns.Count).End(xlToLeft).Column
            If lastCol > highestNumOfSheets Then highestNumOfSheets = lastCol
        Next i

        groupArr = .Range(.Cells(2, 1), .Cells(lastRow, highestNumOfSheets)).Value2
    End With

    Application.ScreenUpdating = False

    For i = LBound(groupArr) To UBound(groupArr)
        fileName = "CS Data Sheet " & Format(Date, "mmmyy") & "-" & groupArr(i, 1) & ".xlsm"
        reNamed = Environ("UserProfile") & "\Desktop\" & fileName

        removedSheet1 = False   ' Reset this on each new workbook created
        Set wb2 = Workbooks.Add

        ' Pick all the sheet names for the current group
        For j = 2 To UBound(groupArr, 2)

            ' Skip empty values from array (if it's the case) and skip missing sheets
            If Trim(groupArr(i, j)) <> vbNullString And SheetExists(groupArr(i, j)) Then
                ThisWorkbook.Worksheets(groupArr(i, j)).Copy Before:=wb2.Worksheets(1)

                ' Remove Sheet1 from the new Workbook
                If removedSheet1 = False Then
                    With Application
                        .DisplayAlerts = False
                        wb2.Worksheets("Sheet1").Delete
                        removedSheet1 = True
                        .DisplayAlerts = True
                    End With
                End If
            End If
        Next j

        ' Here you might need an error handler if you think you're going to run the macro multiple times in the same day
        ' If the file exists already this will throw an error
        ' A quick lazy way is to add time (including seconds) when you define the file name above

        wb2.SaveAs fileName:=reNamed, FileFormat:=xlOpenXMLWorkbookMacroEnabled
        wb2.Close
        If Not wb2 Is Nothing Then Set wb2 = Nothing
    Next i

    Application.ScreenUpdating = True
End Sub

Function SheetExists(ByVal sheetName As String) As Boolean
    Dim ws As Worksheet

    On Error Resume Next
    Set ws = ThisWorkbook.Worksheets(sheetName)
    On Error GoTo 0

    If Not ws Is Nothing Then
        SheetExists = True
        Set ws = Nothing
    End If
End Function

Конечно, его можно подправить, с обработкой ошибок и другими проверками (в зависимости от того, чего вы хотите достичь целиком), но это должно дать вам альтернативное представление о вашем коде.

РЕДАКТИРОВАТЬ: Добавлена ​​функция проверки наличия листа.

0 голосов
/ 25 апреля 2020

По моему опыту, если вы обнаружите в своем коде жестко заданные значения, такие как имена листов, имена групп и другие данные, это будет трудно поддерживать. Добавление дополнительных групп или перестановка листов в каждой группе становится проблематичным c. Я рекомендую создать (возможно скрытый) рабочий лист, который сопоставит имена ваших рабочих листов с группами. Затем у вас есть небольшой набор кода, который работает непосредственно с этим.

Мой пример данных настроен так:

enter image description here

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

(Это Subs и Functions. Для моего собственного кода я, вероятно, создал бы класс VBA для обработки данных объектно-ориентированным способом.)

Итак, есть Private Sub для получения данных:

Option Explicit

Private groupData As Variant

Private Sub GetGroupData()
    Const GROUP_WS_NAME As String = "GroupMap"
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets(GROUP_WS_NAME)

    Dim lastRow As Long
    Dim lastCol As Long
    With ws
        '--- how many columns of groups?
        lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
        lastRow = .UsedRange.Find("*", , , , xlByRows, xlPrevious).Row
        groupData = .Range("A1").Resize(lastRow, lastCol).Value
    End With
End Sub

Теперь легко определить, сколько существует групп:

Public Function NumberOfGroups() As Long
    If IsEmpty(groupData) Then GetGroupData
    NumberOfGroups = UBound(groupData, 2)
End Function

И сколько элементов в конкретной группе:

Public Function NumberInGroup(ByVal groupNumber As Long)
    If IsEmpty(groupData) Then GetGroupData
    '--- count the number of array values that have data
    Dim i As Long
    For i = LBound(groupData, 1) To UBound(groupData, 1)
        If groupData(i, groupNumber) = vbNullString Then
            '--- we found the first empty cell in this array, we're done
            Exit For
        Else
            NumberInGroup = NumberInGroup + 1
        End If
    Next i
    '--- subtract one to discount the header value
    NumberInGroup = NumberInGroup - 1
End Function

Самым простым является получение значения любой группы:

Public Function GetGroupValue(ByVal groupNumber As Long, _
                              ByVal groupIndex As Long) As Variant
    If IsEmpty(groupData) Then GetGroupData
    '--- always add one to the index to account for the header value
    GetGroupValue = groupData(groupIndex + 1, groupNumber)
End Function

Обратите внимание на проверку для If IsEmpty(groupData) Then GetGroupData в начале каждого метода. Это гарантирует, что массив groupData всегда загружается при необходимости.

Этот пример дает быструю проверку (в другом модуле кода):

Option Explicit

Sub test()
    Dim totalGroups As Long
    totalGroups = NumberOfGroups()

    Dim i As Long
    Dim j As Long
    For i = 1 To totalGroups
        Dim totalInGroup As Long
        totalInGroup = NumberInGroup(i)
        For j = 1 To totalInGroup
            Debug.Print "group " & i & " = " & GetGroupValue(i, j)
        Next j
    Next i
End Sub

Вот весь код данных группы модуль в одном блоке:

Option Explicit

Private groupData As Variant

Private Sub GetGroupData()
    Const GROUP_WS_NAME As String = "GroupMap"
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets(GROUP_WS_NAME)

    Dim lastRow As Long
    Dim lastCol As Long
    With ws
        '--- how many columns of groups?
        lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
        lastRow = .UsedRange.Find("*", , , , xlByRows, xlPrevious).Row
        groupData = .Range("A1").Resize(lastRow, lastCol).Value
    End With
End Sub

Public Function NumberOfGroups() As Long
    If IsEmpty(groupData) Then GetGroupData
    NumberOfGroups = UBound(groupData, 2)
End Function

Public Function NumberInGroup(ByVal groupNumber As Long)
    If IsEmpty(groupData) Then GetGroupData
    '--- count the number of array values that have data
    Dim i As Long
    For i = LBound(groupData, 1) To UBound(groupData, 1)
        If groupData(i, groupNumber) = vbNullString Then
            '--- we found the first empty cell in this array, we're done
            Exit For
        Else
            NumberInGroup = NumberInGroup + 1
        End If
    Next i
    '--- subtract one to discount the header value
    NumberInGroup = NumberInGroup - 1
End Function

Public Function GetGroupValue(ByVal groupNumber As Long, ByVal groupIndex As Long) As Variant
    If IsEmpty(groupData) Then GetGroupData
    '--- always add one to the index to account for the header value
    GetGroupValue = groupData(groupIndex + 1, groupNumber)
End Function
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...