#excel #vba #optimization #foreach
#преуспеть #vba #оптимизация #foreach
Вопрос:
Я знаю об условном форматировании, но оно не дает мне вариантов, которые я ищу: а именно, возможность вручную изменять цвет заливки ячейки (в затронутых ячейках) на основе того, как цвет другой ячейки, и при этом стандартный цвет заливки, если я ничего не делаю. У меня есть этот код VBA для одной строки (см. Ниже), и он работает, хотя у меня такое чувство, что он сложен сам по себе. Теперь я хочу сделать то же самое еще для 149 строк, но код, очевидно, становится слишком сложным. Как я могу этого добиться? Неправильно ли помещать это в SelectionChange?
Код:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim Cell As Range
For Each Cell In Range("F7:PB7")
If Cell.Value < Range("D8").Value Or Cell.Value > Range("E8").Value Then
Cell.Offset(1, 0).Interior.ColorIndex = 0
End If
If Cell.Value >= Range("D8").Value And Cell.Value <= Range("E8").Value Then
If Range("B8").Interior.ColorIndex < 0 Then
Cell.Offset(1, 0).Interior.ColorIndex = 15
Else
If Range("B8").Interior.ColorIndex >= 0 Then
Cell.Offset(1, 0).Interior.Color = Range("B8").Interior.Color
End If
End If
End If
... et cetera next row ...
Next Cell
End Sub
С наилучшими пожеланиями!
Комментарии:
1. Добро пожаловать в SO. Итак, первый вопрос; нужно ли его использовать
SelectionChange
? Не могли бы вы поместить его в событие, которое запускается реже? Во-вторых, вы упомянули, что код станет сложным еще для 149 строк, это потому, что для этих строк нужна другая логика / код?2. Я чувствую, что вам следует добавить критерии, чтобы это не срабатывало при перемещении данных и т. Д., Добавление чего-то вроде a
If Not Intersect(Target, Range("F:PB")) Is Nothing Then Exit Sub
помогло бы в этом.3. Не понимая взаимосвязи между строками 7 и 8, я не знаю, можем ли мы экстраполировать / интерполировать изменение этого для дополнительных строк. Если вы хотите добавить критерий для удаления всех остальных строк или если вы хотите использовать функцию на основе
Target.Row
, это может оказаться полезным, что также позволяет исключить четные / нечетные строки из запуска события.4. Неясно, когда это должно выполняться. Если вы хотите запустить его вручную или с помощью кнопки (command), вам нужно поместить код в стандартный модуль. Если нет, вам, возможно, потребуется рассмотреть несколько сценариев, например, когда изменение происходит в
F7:PB7
или изменение происходит в столбцахD
илиE
. В зависимости от того, есть ли значения или формулы в этих местах, может быть создано подходящее решение. Нет события, которое могло бы сработать, если вы измените цвет в столбцеB
. Попробуйте дополнительно объяснить логику и решить проблему «значений или формул», лучше всего в вашем ответе, который вы можете отредактировать в любое время.5. Было бы неплохо, если бы мне не нужно было запускать макрос (нажимать кнопку), поэтому на практике я ищу поведение, которое мы видим при условном форматировании.
Ответ №1:
Попробуйте это. Я получаю цвет по умолчанию для каждой строки из ColA.
Все это находится в модуле worksheet code:
Option Explicit
Const RW_DATES As Long = 7 'row with headers and dates
Const COL_NAME As Long = 2 'column with person's name
Const COL_START_DATE As Long = 4 'column with start date
Const COL_DATE1 As Long = 6 '1st date on header row
Const NUM_ROWS As Long = 150 'how many rows?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range, rng As Range, rngDates As Range, i As Long
Dim startDate, endDate, rw As Range, arrDates, rngRowDates As Range
Dim CheckAll As Boolean, hiliteColor As Long, hiliteName As String
Dim cName As Range, selName, selColor As Long
CheckAll = Target Is Nothing 'called from selection_change?
If Not CheckAll Then
'Was a cell changed? see if any start/end date cells were changed
Set rng = Application.Intersect(Target, _
Me.Cells(RW_DATES 1, COL_START_DATE).Resize(NUM_ROWS, 2))
If rng Is Nothing Then Exit Sub 'nothing to do in this case
Else
'called from Selection_change: checking *all* rows
Set rng = Me.Cells(RW_DATES 1, COL_START_DATE).Resize(NUM_ROWS)
End If
Debug.Print "ran", "checkall=" amp; CheckAll
'header range with dates
Set rngDates = Me.Range(Me.Cells(RW_DATES, COL_DATE1), _
Me.Cells(RW_DATES, Columns.Count).End(xlToLeft))
arrDates = rngDates.Value 'read dates to array
Set cName = NameHiliteCell() 'see if there's a hilited name
If Not cName Is Nothing Then
selName = cName.Value
selColor = cName.Interior.Color
End If
'loop over each changed row
For Each rw In rng.EntireRow.Rows
Set rngRowDates = rw.Cells(COL_DATE1).Resize(1, rngDates.Columns.Count)
rngRowDates.Interior.ColorIndex = xlNone 'clear by default
startDate = rw.Cells(COL_START_DATE).Value 'read the dates for this row
endDate = rw.Cells(COL_START_DATE 1).Value
'determine what color the bar should be
If Len(selName) > 0 And selName = rw.Cells(COL_NAME).Value Then
hiliteColor = selColor
Else
hiliteColor = rw.Cells(1).Interior.Color
End If
If startDate > 0 And endDate > 0 Then
i = 0
For Each c In rngRowDates.Cells
i = i 1
If arrDates(1, i) >= startDate And arrDates(1, i) <= endDate Then
c.Interior.Color = hiliteColor
End If
Next c
End If
Next rw
End Sub
'just calls Worksheet_Change; add some delay to reduce frequency of firing
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Static lastrun As Date
If lastrun = 0 Then lastrun = Now
If Now - lastrun > (1 / 86400) Then
lastrun = Now
Worksheet_Change Nothing
End If
End Sub
'find the first name cell which has any fill and return it
Function NameHiliteCell() As Range
Dim c As Range
For Each c In Me.Cells(RW_DATES 1, COL_NAME).Resize(NUM_ROWS)
If Not c.Interior.ColorIndex = xlNone Then
Set NameHiliteCell = c
Exit Function
End If
Next c
End Function
Мой диапазон тестирования:
Комментарии:
1. @TomWilliams, ваш код работает безупречно! Огромное спасибо! Какая сильная поддержка такого форума. Еще раз спасибо! 🙂
2. Добро пожаловать — для меня это был забавный мини-проект.
Ответ №2:
Будет ли что-то подобное лучше? Он будет срабатывать только при изменении значения в диапазоне F7:PB7
.
Он не будет срабатывать, если значение ячейки обновляется с помощью формулы (для этого вам нужно посмотреть на ячейку, которую вы изменили, чтобы обновить формулу).
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count = 1 Then 'Only fire if a single cell is changed.
If Not Intersect(Target, Range("F7:PB154")) Is Nothing Then
MsgBox Target.Address 'Test
'Your code - looking at Target rather than each Cell in range.
End If
End If
End Sub
Редактировать: обновил диапазон, чтобы он просматривал более одной строки, но теперь думаю, что я должен удалить ответ из-за нечетных / четных строк, которые указывает @Cyril, и т.д….. сейчас это не похоже на полный ответ.
Комментарии:
1. Дело в том, что диапазон F7: PB7 содержит уникальные значения даты (F7: 1 декабря 2021 года, G7: 2 декабря 2021 года, H7: 3 декабря 2021 года и т.д.) и никогда не будет изменен. Столбцы D и E содержат значения «дата начала» и «дата окончания». Если D8 равно «1 декабря 2021 года», а E8 равно «10 декабря 2021 года», диапазон F8: O8 заполняется «цветом по умолчанию» (в моем коде ColorIndex 15, то есть светло-серым). Теперь, если я заполню B8 зеленым цветом, я хочу, чтобы диапазон F8: O8 изменился на зеленый. Если я изменю цвет заливки в B8, цвет в диапазоне F8: O8 будет следующим. Если я удалю цвет в B8, цвет заливки в диапазоне F8: O8 вернется к «цвету по умолчанию». Работает для строки 8, но мне нужно больше строк.
2. Кроме того, если я удалю значения в D8 («дата начала») и E8 («дата окончания»), я хочу, чтобы цвет заливки в F8: O8 также был удален.
3. Вот макет и визуализация того, чего я пытаюсь достичь: ibb.co/tPqNRCs