Определить родительского ребенка

Всем привет.

Я создал макрос VBA в Excel, чтобы указать значение отступа в столбце B (на вкладке «Выравнивание» экрана «Формат ячейки») и пытаюсь установить родительский/дочерний элемент в столбце C. Выходные данные должны соответствовать столбцу D, но мне трудно получить логика. Он должен смотреть на столбец B, чтобы построить родительский/дочерний элемент, поскольку столбец A иногда может иметь разные значения.

Любая помощь будет ОЧЕНЬ оценена!

Sub GetIndentLevels()
Dim ws As Worksheet
Dim lastRow As Long
Dim i As Long
Dim rng As Range
Dim indentLevel As Integer

' Set the worksheet
Set ws = ThisWorkbook.Sheets("Target") ' Change "Sheet1" to your sheet name

' Find the last row with data in column A
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

' Loop through each row in column A
For i = 1 To lastRow
    ' Get the range for the current cell in column A
    Set rng = ws.Cells(i, "A")
    
    ' Get the indent level of the cell
    indentLevel = rng.indentLevel
    
    ' Write the indent level to column B
    ws.Cells(i, "B").Value = indentLevel
Next i
End Sub

Sub DetermineParentChild()
Dim ws As Worksheet
Dim lastRow As Long
Dim currentRow As Long
Dim currentValue As Variant
Dim nextValue As Variant
Dim markParent As Boolean

' Set the worksheet
Set ws = ThisWorkbook.Sheets("Target")

' Find the last row in column A
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

' Initialize the markParent variable
markParent = False

' Loop through each row in column A
For currentRow = 1 To lastRow
    ' Get the value of column B for the current row
    currentValue = ws.Cells(currentRow, 2).Value
    
    ' Check if the current value is numeric
    If IsNumeric(currentValue) Then
        ' Check if the next row exists and get its value
        If currentRow < lastRow Then
            ' Get the value of column B for the next row
            nextValue = ws.Cells(currentRow + 1, 2).Value
        Else
            ' If there is no next row, set nextValue to a default value
            nextValue = ""
        End If
        
        ' Check if the current value should be marked as "P" (Parent) or "C" (Child)
        If markParent Then
            ws.Cells(currentRow, 3).Value = "P"
        Else
            If nextValue = currentValue + 1 Then
                ws.Cells(currentRow, 3).Value = "P"
            Else
                ws.Cells(currentRow, 3).Value = "C"
            End If
        End If
        
        ' Update the markParent variable for the next iteration
        markParent = (nextValue <> currentValue + 1)
    Else
        ' If the current value is not numeric, mark it as "C" (Child)
        ws.Cells(currentRow, 3).Value = "C"
    End If
Next currentRow
End Sub

Пожалуйста, не загружайте изображения кода/данных/ошибок. Используйте форматирование таблицы для вставки текстовых данных.
Ken White 29.03.2024 04:13

Пожалуйста, покажите заголовок столбца на скриншоте. Каков ваш ожидаемый результат?

taller 29.03.2024 04:26
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
2
2
102
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Родитель-ребенок против Range.IndentLevel

Процедура вызова (пример)

Sub RunParentChild()

    Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Target")
    Dim rg As Range:
    Set rg = ws.Range("A1", ws.Cells(ws.Rows.Count, "A").End(xlUp))
    
    Dim IndentLevels() As Long: IndentLevels = GetIndentLevels(rg)
        
    With rg.EntireRow
        .Columns("B").Value = IndentLevels ' not necessary
        .Columns("C").Value = GetIndentedParentChildFromColumn(rg, IndentLevels)
    End With

End Sub

Вызываемые (вспомогательные) процедуры

Function GetIndentLevels(rg As Range) As Long()
    
    Dim rCount As Long: rCount = rg.Rows.Count
    Dim cCount As Long: cCount = rg.Columns.Count
    
    Dim Data() As Long: ReDim Data(1 To rCount, 1 To cCount)
    
    Dim r As Long, c As Long
    
    For r = 1 To rCount
        For c = 1 To cCount
            Data(r, c) = rg.Cells(r, c).IndentLevel
        Next c
    Next r
    
    GetIndentLevels = Data

End Function
  • Можно сократить следующую функцию, исключив переменные i и IsParent, весь оператор If r = rCount Then и строку перед ним. Тогда вместо If IsParent Then можно было бы использовать довольно нечитаемый (в VBA True = -1)...

     If IndentLevels(r + 2 * (r = rCount) + 1, 1) > IndentLevels(r, 1) Then
    
  • Даже следующий однострочник будет работать:

     cData(r, 1) = IIf(IndentLevels(r + 2 * (r = rCount) + 1, 1) > IndentLevels(r, 1), "P", "C") 
    

    Оба не рекомендуются!

Function GetIndentedParentChildFromColumn( _
    rg As Range, _
    IndentLevels() As Long, _
    Optional ColumnIndex As Long = 1) _
As Variant
    
    Dim cData As Variant: cData = GetRange(rg.Columns(ColumnIndex))
    Dim rCount As Long: rCount = UBound(cData, 1)
    
    Dim r As Long, i As Long, IsFirstFound As Boolean, IsParent As Boolean
    
    For r = 1 To rCount
        If IsFirstFound Then
            i = IndentLevels(r, 1)
            If r = rCount Then
                IsParent = IndentLevels(r - 1, 1) > i
            Else
                IsParent = IndentLevels(r + 1, 1) > i
            End If
            If IsParent Then
                cData(r, 1) = "P"
                IsParent = False ' reset for the next iteration
            Else
                cData(r, 1) = "C"
            End If
        Else
            cData(r, 1) = "P"
            IsFirstFound = True
        End If
    Next r

    GetIndentedParentChildFromColumn = cData
    
End Function
Function GetRange(rg As Range) As Variant()
    If rg.Rows.Count + rg.Columns.Count = 2 Then
        ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value: GetRange = Data
    Else
        GetRange = rg.Value
    End If
End Function

Следует отметить одну вещь: он должен учитывать только уровни отступов, поскольку SUB может быть другим именем элемента для других приложений. Когда я удаляю логику SUB, все работает, за исключением некоторых. Я пытаюсь добавить еще один оператор CASE, который говорит: «Если уровень отступа текущей строки меньше уровня отступа предыдущей строки, но уровень отступа следующей строки такой же, как текущий, то C». но не повезло с остальным. Результаты опубликую в исходном посте.

user7571572 29.03.2024 14:58

Извините, я реализовал упомянутую вами логику и исключил сравнения SUB. Кажется, что в данных в столбце E значение E20 недопустимо, т. е. оно не может быть P. Пожалуйста, поправьте меня, если я ошибаюсь.

VBasic2008 29.03.2024 19:13

Правильно. E20 должно быть C. Большое спасибо!

user7571572 30.03.2024 23:10
Ответ принят как подходящий
  • Ваш код довольно близок к завершению. Вы получите ожидаемый результат, удалив логику markParent.
Option Explicit

Sub DetermineParentChild()
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim currentRow As Long
    Dim currentValue As Variant
    Dim nextValue As Variant
    Dim markParent As Boolean
    ' Set the worksheet
    Set ws = ThisWorkbook.Sheets(1)
    ' Find the last row in column A
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    ' Initialize the markParent variable
    markParent = False
    ' Loop through each row in column A
    For currentRow = 2 To lastRow  ' ** start from row 2
        ' Get the value of column B for the current row
        currentValue = ws.Cells(currentRow, 2).Value
        ' Check if the current value is numeric
        If IsNumeric(currentValue) Then
            ' Check if the next row exists and get its value
            If currentRow < lastRow Then
                ' Get the value of column B for the next row
                nextValue = ws.Cells(currentRow + 1, 2).Value
            Else
                ' If there is no next row, set nextValue to a default value
                nextValue = ""
            End If
            ' Check if the current value should be marked as "P" (Parent) or "C" (Child)
            ' ***
            If nextValue = currentValue + 1 Then
                ws.Cells(currentRow, 3).Value = "P"
            Else
                ws.Cells(currentRow, 3).Value = "C"
            End If
            ' ***
        Else
            ' If the current value is not numeric, mark it as "C" (Child)
            ws.Cells(currentRow, 3).Value = "C"
        End If
    Next currentRow
End Sub

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