【问题标题】:How can I optimize this code in Excel VBA?如何在 Excel VBA 中优化此代码?
【发布时间】:2021-12-02 12:37:37
【问题描述】:

我知道条件格式,但它没有给我我正在寻找的选项:即,可以根据另一个单元格的颜色手动更改单元格填充颜色(在受影响的单元格中),以及如果我什么都不做,那就是标准的填充颜色。我有一个单行的 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

最好的问候!

【问题讨论】:

  • 欢迎来到 SO。所以,第一个问题;它必须使用SelectionChange吗?你能把它放在一个不太经常触发的事件中吗?第二;您提到另外 149 行的代码会变得复杂,是因为这些行需要不同的逻辑/代码吗?
  • 我觉得您应该添加条件,这样就不会在您移动数据等时触发,添加 If Not Intersect(Target, Range("F:PB")) Is Nothing Then Exit Sub 之类的内容会有所帮助。
  • 在不了解第 7 行和第 8 行之间的关系的情况下,我不知道我们是否可以推断/插值修改它以用于其他行。如果您想添加条件以删除每隔一行,或者如果您想将该函数基于Target.Row,它可能会提供有用的信息,它还允许您消除触发事件的偶数/奇数行。
  • 不清楚何时应该运行。如果您想手动或通过(命令)按钮运行它,您需要将代码放入标准模块中。如果没有,您可能需要涵盖一些场景,例如当F7:PB7 发生更改或DE 列发生更改时。根据这些位置是否存在值或公式,可以创建适当的解决方案。如果您更改B 列中的颜色,则不会触发任何事件。尝试另外解释逻辑并解决“值或公式问题”,最好是您可以随时编辑的答案。
  • 如果我不必运行宏(单击按钮)那就太好了,所以在实践中,我正在寻找我们在条件格式中看到的那种行为。

标签: excel vba optimization foreach


【解决方案1】:

试试这个。我从 ColA 获取每一行的默认颜色。

这一切都在工作表代码模块中:

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=" & 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

我的测试范围:

【讨论】:

  • @TomWilliams,您的代码完美运行!非常感谢!像这样的论坛是多么强大的支持。再次感谢! :)
  • 不客气 - 对我来说是一个有趣的迷你项目。
【解决方案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 指示的奇数/偶数行等等......这看起来并不完整现在回答。

【讨论】:

  • 问题是,范围 F7:PB7 拥有唯一的日期值(F7:2021 年 12 月 1 日,G7:2021 年 12 月 2 日,H7:2021 年 12 月 3 日等)并且永远不会更改。 D 和 E 列包含“开始日期”和“结束日期”值。如果 D8 是“2021 年 12 月 1 日”并且 E8 是“2021 年 12 月 10 日”,则 F8:O8 范围将填充“默认颜色”(在我的代码 ColorIndex 15 中,即浅灰色)。现在,如果我用绿色填充 B8,我希望范围 F8:O8 变为绿色。如果我更改 B8 中的填充颜色,则 F8:O8 范围内的颜色如下。如果我删除 B8 中的颜色,F8:O8 范围内的填充颜色将恢复为“默认颜色”。适用于第 8 行,但我需要更多行。
  • 另外,如果我删除 D8(“开始日期”)和 E8(“结束日期”)中的值,我希望 F8:O8 中的填充颜色也被删除。
  • 这是我想要实现的布局和可视化:ibb.co/tPqNRCs
猜你喜欢
  • 1970-01-01
  • 2011-04-09
  • 1970-01-01
  • 1970-01-01
  • 2016-12-10
  • 1970-01-01
  • 1970-01-01
  • 2018-04-23
  • 1970-01-01
相关资源
最近更新 更多