Всем привет.
Я создал макрос 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
Пожалуйста, покажите заголовок столбца на скриншоте. Каков ваш ожидаемый результат?


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». но не повезло с остальным. Результаты опубликую в исходном посте.
Извините, я реализовал упомянутую вами логику и исключил сравнения SUB. Кажется, что в данных в столбце E значение E20 недопустимо, т. е. оно не может быть P. Пожалуйста, поправьте меня, если я ошибаюсь.
Правильно. E20 должно быть C. Большое спасибо!
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