Excel – VBA – цикл For работает неправильно. Я не знаю точной проблемы

У меня есть этот код ниже в модуле сценариев Excel VBA:


Sub SynchronizeDataValidations()
    
    Dim targetSheets As Variant
    Set targetSheets = ThisWorkbook.Worksheets("TEST")
    
    Dim sheets As Collection
    Set sheets = GetCurrentSheets(targetSheets)

    Dim searchRanges As Collection
    Set searchRanges = GetSearchRanges(sheets)
    
    Dim Validations As Collection
    Set Validations = GetValidations(searchRanges)


End Sub



Private Function GetCurrentSheets(Optional targetSheets As Variant) As Collection
    Dim sheets As New Collection ' Collection to store worksheet objects
    Dim sheet As Worksheet

    ' Check if targetSheets is provided
    If IsMissing(targetSheets) Then
        ' Process all sheets in the workbook
        For Each sheet In ThisWorkbook.Worksheets
            sheets.Add sheet ' Add the worksheet object itself
        Next sheet
    ElseIf TypeName(targetSheets) = "Worksheet" Then
        ' Process a single specified worksheet
        sheets.Add targetSheets ' Add the single worksheet object
    Else
        ' Assume targetSheets is a collection or array of worksheets
        For Each sheet In targetSheets
            sheets.Add sheet ' Add each worksheet object from the collection/array
        Next sheet
    End If
    
    Debug.Print "Got Sheets"

Set GetCurrentSheets = sheets

End Function


Private Function GetSearchRanges(sheets As Collection) As Collection
    
    Dim searchRanges As New Collection
    
    
    ' Iterate through the collected sheet objects
    For Each sheet In sheets
    
        ' Find the last used cell in the sheet
        Dim lastCell As Range
        Set lastCell = sheet.UsedRange.SpecialCells(xlCellTypeLastCell)
    
        ' Skip the sheet if no cell with content was found
        If lastCell Is Nothing Then
            Debug.Print "Skipping sheet '" & sheet.Name & "' - no data found"
            GoTo NextSheet
        End If
    
        ' Define the range to iterate over based on the last used cell
        Dim rng As Range
        Set rng = sheet.Range("A1:" & lastCell.Address)
        Debug.Print "Search Range:" & rng.Address(External:=True)
    
        ' Add the array to the searchRanges collection
        searchRanges.Add rng
        
NextSheet:
    Next sheet
    Debug.Print "Got SearchRanges"

Set GetSearchRanges = searchRanges

End Function

Private Function GetValidations(searchRanges As Collection) As Collection
 ' Create a collection to store validation data
    Dim Validations As New Collection

    For Each searchRange In searchRanges
    
        Dim cell As Range
        
        For Each cell In searchRange
            If HasValidation(cell) Then
                If cell.validation.Type = xlValidateList And Not IsEmpty(cell.validation.Formula1) Then
                
                    Debug.Print "Checking: " & cell.Parent.Name & cell.Address
                    Dim sourceRange As Range
                    Set sourceRange = Range(cell.validation.Formula1)
                    
                    

                    If Not sourceRange Is Nothing Then
                        Debug.Print "Source Found for " & sourceRange.Address(External:=True) & "  and it's " & cell.validation.Formula1
                        
                        Dim validation As Variant
                        Dim foundMatch As Boolean  ' Flag to track if a match is found
                        
                        ' Iterate through existing validations
                        For Each validation In Validations
                            ' Compare sheet-level addresses, ignoring workbook qualifier
                            If validation(1).Address(External:=False) = sourceRange.Address(External:=False) Then
                                Debug.Print "validation exists in validations"
                                ' If it exists, add the new applied range to its collection
                                validation(2).Add cell
                                Debug.Print cell.Parent.Name & "!" & cell.Address & " was added"
                                foundMatch = True
                                GoTo NextCell ' Jump to the next cell if a match is found
                            End If
                        Next validation
                        
                        ' If no match was found, add a new validation
                        If Not foundMatch Then
                            Debug.Print "no validation exists"
                        
                            ' Create a new collection to store applied ranges for this validation
                            Dim appliedRanges As New Collection
                            appliedRanges.Add cell
                        
                            ' Create a new validation data collection
                            Dim validationData As New Collection
                            validationData.Add sourceRange
                            validationData.Add appliedRanges
                        
                            ' Add the new validation to the validations collection
                            Validations.Add validationData
                            Debug.Print "Added new Validation"
                        End If
                        
NextCell:
                        ' Continue to the next cell in the outer loop
                    End If
                    
                End If
            End If
        Next cell
    Next searchRange



    ' Debug print the validations collection
    Debug.Print "=== Validations Collection == = "
    Debug.Print Validations.Count & " validations in collection"
    For Each validation In Validations
    
        Set sourceRange = validation(1)
        Set appliedRanges = validation(2)
    
        Debug.Print "Source Range: " & sourceRange.Address(External:=True) ' Include sheet name
        Debug.Print "Applied Ranges:"
        For Each appliedRange In appliedRanges
            Debug.Print "  - " & appliedRange.Address(External:=True)
        Next appliedRange
        Debug.Print "-----------------------"
    Next validation
    
Set GetValidations = Validations

End Function


Function HasValidation(ByRef cellAddress As Range) As Boolean
    Dim t As Variant
    t = Null

    On Error Resume Next
    t = cellAddress.validation.Type
    On Error GoTo 0

    HasValidation = Not IsNull(t)
End Function

Это вывод debug.print:

List Created
New Data Placed
Got Sheets
Got Sheets
Search Range:[Validations.xlsm]TEST!$A$1:$B$5
Got SearchRanges
Checking: TEST$A$1
Source Found for [Validations.xlsm]SETUP!$B$1:$B$6  and it's =SETUP!$B$1:$B$6
no validation exists
Added new Validation
Checking: TEST$B$1
Source Found for [Validations.xlsm]SETUP!$A$1:$A$2  and it's =SETUP!$A$1:$A$2
no validation exists
Added new Validation
Checking: TEST$A$2
Source Found for [Validations.xlsm]SETUP!$B$1:$B$6  and it's =SETUP!$B$1:$B$6
validation exists in validations
TEST!$A$2 was added
Checking: TEST$B$2
Source Found for [Validations.xlsm]SETUP!$A$1:$A$2  and it's =SETUP!$A$1:$A$2
Checking: TEST$A$3
Source Found for [Validations.xlsm]SETUP!$B$1:$B$6  and it's =SETUP!$B$1:$B$6
validation exists in validations
TEST!$A$3 was added
Checking: TEST$B$3
Source Found for [Validations.xlsm]SETUP!$A$1:$A$2  and it's =SETUP!$A$1:$A$2
Checking: TEST$A$4
Source Found for [Validations.xlsm]SETUP!$B$1:$B$6  and it's =SETUP!$B$1:$B$6
validation exists in validations
TEST!$A$4 was added
Checking: TEST$B$4
Source Found for [Validations.xlsm]SETUP!$A$1:$A$2  and it's =SETUP!$A$1:$A$2
Checking: TEST$A$5
Source Found for [Validations.xlsm]SETUP!$A$1:$A$2  and it's =SETUP!$A$1:$A$2
Checking: TEST$B$5
Source Found for [Validations.xlsm]SETUP!$A$1:$A$2  and it's =SETUP!$A$1:$A$2
=== Validations Collection ===
2 validations in collection
Source Range: [Validations.xlsm]SETUP!$B$1:$B$6
Applied Ranges:
  - [Validations.xlsm]TEST!$A$1
  - [Validations.xlsm]TEST!$B$1
  - [Validations.xlsm]TEST!$A$2
  - [Validations.xlsm]TEST!$A$3
  - [Validations.xlsm]TEST!$A$4
-----------------------
Source Range: [Validations.xlsm]SETUP!$B$1:$B$6
Applied Ranges:
  - [Validations.xlsm]TEST!$A$1
  - [Validations.xlsm]TEST!$B$1
  - [Validations.xlsm]TEST!$A$2
  - [Validations.xlsm]TEST!$A$3
  - [Validations.xlsm]TEST!$A$4
-----------------------

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

Тестовый файл можно получить здесь: https://drive.google.com/file/d/1IWKKfN6zY1zjUMNcz_OGnQ3u24Qf1Ira/view?usp=drive_link

Пожалуйста, помогите.

Я перепробовал все комбинации циклов for и операторов go to и if. Я много пробовал, но не могу понять. слишком тупой.

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

Не используйте Dim ... As New... Используйте: Dim validationData As Collection: Set validationData = New Collection

Rory 03.09.2024 09:26
.UsedRange.SpecialCells(xlCellTypeLastCell) никогда не бывает ничем.
TinMan 03.09.2024 10:43

Спасибо, Рори. Сделали это изменение синтаксиса. Тинман, что возвращается, когда лист пуст? Как мне тогда это подтвердить?

JesseRigon 03.09.2024 14:43

У SpecialCells есть вариант xlCellTypeAllValidation, вот он и есть.

Tim Williams 03.09.2024 18:50
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
1
4
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Другой подход, но у меня это сработало:

Option Explicit

Sub SynchronizeDataValidations()
    
    Dim colWs As New Collection, Validations As Object
    
    colWs.Add ThisWorkbook.Worksheets("TEST")
    colWs.Add ThisWorkbook.Worksheets("TEST (2)")
    Set Validations = GetValidations(colWs)
    
    DumpValidations Validations

End Sub


Private Function GetValidations(shts As Collection) As Object
    Dim dict As Object, c As Range, ws As Worksheet, sourceRange As Range, k As String, validation As Variant
    Dim frm As String
    
    Set dict = CreateObject("scripting.dictionary")
    For Each ws In shts
        For Each c In ws.Cells.SpecialCells(xlCellTypeAllValidation).Cells
            If c.validation.Type = xlValidateList And Not IsEmpty(c.validation.Formula1) Then
                frm = c.validation.Formula1
                Set sourceRange = Nothing
                On Error Resume Next         'ignore error if frm is not a range
                Set sourceRange = Range(frm)
                On Error GoTo 0              'stop ignoring errors
                If sourceRange Is Nothing Then
                    Debug.Print "Non-range source: " & frm, c.Address(external:=True)
                Else
                    k = sourceRange.Address(external:=True)
                    'new source range?
                    If Not dict.exists(k) Then dict.Add k, New Collection
                    dict(k).Add c 'add this cell to the appropriate collection
                End If 'have source range
            End If     'list type
        Next c
    Next ws
    Set GetValidations = dict
End Function

Sub DumpValidations(vals As Object)
    Dim k, c As Range
    ' Debug print the validations collection
    Debug.Print "=== Validations Collection == = "
    Debug.Print vals.Count & " validations in collection"
    
    For Each k In vals
        Debug.Print "-----------------------"
        Debug.Print "Source: " & k
        For Each c In vals(k)
            Debug.Print , c.Address(external:=True)
        Next c
    Next k
End Sub

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

JesseRigon 03.09.2024 21:22

Тим Уильямс Есть идеи, почему мой предыдущий код не работал? Я проверил логику несколько раз и не нашел проблемы.

JesseRigon 03.09.2024 21:24

Перемещение объявлений переменных в начало метода и применение предложения @Rory исправили ваш код для меня.

Tim Williams 04.09.2024 02:37

Другие вопросы по теме