【问题标题】:Why is my conditional format offset when added by VBA?为什么 VBA 添加时我的条件格式偏移?
【发布时间】:2012-09-14 08:49:13
【问题描述】:

我试图添加这样的条件格式:

如果表达式=($G5<>"") 则将内部设置为绿色,将其用于$A$5:$H$25。

试过了,工作正常,正如预期的那样,然后尝试使用以下代码将其调整为 VBA-Code,它正在工作,但不符合预期:

With ActiveSheet.UsedRange.Offset(1)
  .FormatConditions.Delete
  'set used row range to green interior color, if "Erledigt Datum" is not empty
  With .FormatConditions.Add(Type:=xlExpression, _
                             Formula1:="=($" & cstrDefaultProgressColumn & _
                                                      .row & "<>"""")")
        .Interior.ColorIndex = 4
      End With
End With

问题是.row 在调试时提供了正确的行,但是我添加的条件公式似乎关闭了一行或多行 - 取决于我设置行的解决方案。所以我最终得到了一个条件格式,它与行有一个偏移量,应该已经被格式化了。

然后在对话框中是 =($G6&lt;&gt;"") 或 G3 或 G100310 或类似的东西。但不是我想要的 G5。

将行必须设置为dynamicall,因为这用于在不同的工作表上设置条件格式,这些工作表的数据可以从不同的行开始。

我怀疑我的With 安排,但它没有解决这个问题。

编辑:更具体地说,这不是一个 UsedRange 问题,与此有同样的问题:

Dim rngData As Range
Set rngData = ActiveSheet.Range("A:H") 'ActiveSheet.UsedRange.Offset(1)

rngData.FormatConditions.Delete

With rngData.FormatConditions.Add(Type:=xlExpression, _
                                  Formula1:="=($" & cstrDefaultProgressColumn & _
                                                  1 & "<>"""")")
    .Interior.ColorIndex = 4
End With

我的数据如下所示:

1 -> empty cells
2 -> empty cells
3 -> empty cells
4 -> TitleCols -> A;B;C;...;H
5 -> Data to TitleCols
. .
. .
. .
25

当我在 Excel 2007 上执行此编辑后的代码并在条件对话框中查找公式时,它是 =($G1048571&lt;&gt;"") - 它应该是 =($G1&lt;&gt;""),然后一切正常。

更奇怪的是 - 这是一个可正常工作的代码的编辑版本,用于为每一行添加条件格式。但后来我意识到,可以编写一个表达式,格式化整行或其中的一部分 - 以为这会在一分钟内适应,现在这个^^

编辑: 其他任务信息

我在这里使用条件格式,因为这个函数应该设置一个表格来对用户输入做出反应。因此,如果正确设置并且用户在此表的条件化列中编辑了某些单元格,则对应的行将变为绿色,用于使用的行范围。

现在,因为在主标题行之前可能有行,并且可能有不同数量的数据列,并且目标列可能会改变,所以我当然会使用一些特定的信息。

为了使它们最小化,我确实使用 NamedRanges 来确定正确的偏移量并确定正确的 DefaultProgessColumn

GetTitleRow 用于通过 NamedRange 或 header-contents 来确定 header-row。

With ActiveSheet.UsedRange.Offset(GetTitleRow(ActiveSheet.UsedRange) - _
                                ActiveSheet.UsedRange.Rows(1).row + 1)

更正了我的公式 1,因为我发现之前的构造没有很好地形成。

Formula1:="=(" & Cells(.row, _
           Range(strMatchCol1).Column).Address(RowAbsolute:=False) & _
           "<>"""")"

strMatchCol1 - 是范围的名称。

【问题讨论】:

  • 看来,我必须使用 .row+2 才能做到这一点 - 但为什么?!为什么它只在这个特定的工作表上解决这个问题?! - 我一定在这里遗漏了一些东西 - 有什么想法吗?!非常感谢所有的支持者!
  • 使用 .UsedRange 时要小心 - 请参阅 stackoverflow.com/questions/7423022/… 以及可能的其他一些 SO 线程
  • 我认为您没有从 UsedRange 获得预期的效果。它不会像您预期的那样返回最后一行。你想瞄准什么?
  • 重点是,我不是想得到最后一行。我只关心开始,当我在调试下检查时,UsedRange 提供了我所期望的。我想使用它,这样我就不必指定我的数据集有多宽或多长,所以我很容易适应。
  • Excel 处理单元格引用的方式有些奇怪。您知道粘贴公式时它如何自动更改单元格之间的引用吗?嗯,它正在这样做,但参考是很远的。这发生在第一次运行之后。打开工作簿,设置测试页面,运行代码......它可以工作。再做一次它会失败一点。再做一次,它会失败更多。去挑选它,看看我能不能把它分类。

标签: vba excel conditional-formatting


【解决方案1】:

知道了,哈哈。在进行繁重的工作之前设置 ActiveCell...

ActiveSheet.Range("A1").Activate

Excel 正在拉动它的自动范围调整,这会在添加 FromatCondition 时抛出公式。

【讨论】:

  • 恭喜!就是这样......你赢得了它。但是-我对此并不满意,因为这仍然感觉像是一种解决方法而不是解决方案。但是,我认为这是 Excel 的问题,直到有人证明它不是。
  • 是的,这就是为什么我最初的评论指向某种错误。至少我想称其为“意外后果”,例如“它为什么要这样做?”。
  • 如果您想深入了解 VBA 作为编程,您会注意到由于这个原因和其他原因,使用“ActiveSheet”、“ActiveCell”、“Select”和“Offset”是不好的习惯。 VBA 代码很少保持不变,人们会一直添加和更改内容。不明确命名您的对象(例如,set oBook = workbooks.open("smtg"))但将它们称为 Active 可能会给您带来痛苦。与其选择和偏移,不如定义一个范围(例如,设置 oRange = )并循环遍历该范围。或者先把它加载到一个数组中,然后循环遍历它。
  • @KimGysen 你在某些方面是正确的,我在其他场合使用提到的技术 - 但在这里它似乎不是最佳的。当您不这样认为时,我很高兴看到对此的另一种解决方案。我也支持 select(这就是我不喜欢当前解决方案的原因),但 ActiveSheetActiveCellOffset 有他们的 good 能力,并且在许多情况下都很有用 - 就像这个,即。并不是说 select 没有理由存在,它只是经常被滥用来做不合适的事情任务 - 像 excel 这样的数据库经常被滥用。
  • 简而言之,我的意思是代码执行不应该取决于通常哪个工作表或单元格处于活动状态。我会写一些我想到的代码。当然,这个决定完全取决于你。
【解决方案2】:

条件格式和数据验证表现出这种奇怪行为的原因是因为它们使用的公式超出了正常的计算链。它们必须是这样,您才能在公式中引用活动单元格。如果您在 G1 中,则不能键入 =G1="",因为您将创建循环引用。但在 CF 或 DV 中,您可以键入该公式。与实际公式不同,这些公式与当前单元格无关。

当您输入 CF 公式时,它始终与活动单元格相关。如果,在 CF 中,你制定一个公式

=ISBLANK($G2)

而您在 A5 中,Excel 会将其转换为

=ISBLANK(R[-3]C7)

当它被放入 CF 时,它最终与它所应用的单元格相关。所以在第2行,公式出来了

=ISBLANK($G655536)

(对于 Excel 2003)。它偏移 -3 行,并环绕到电子表格的底部。

您可以使用 Application.ConvertFormula 使公式相对于其他单元格。如果我在第 5 行并且我的范围的开始在第 2 行,我将公式相对于第 8 行。这样,R[-3] 会将公式作为 $G5 放入 A5(从 A8 向上三行)。

Sub test()

    Dim cstrDefaultProgressColumn As String
    Dim sFormula As String

    cstrDefaultProgressColumn = "$G"

    With ActiveSheet.UsedRange.Offset(1)
        .FormatConditions.Delete
        'set used row range to green interior color, if "Erledigt Datum" is not empty

        'Build formula
        sFormula = "=ISBLANK(" & cstrDefaultProgressColumn & .Row & ")"

        'convert to r1c1
        sFormula = Application.ConvertFormula(sFormula, xlA1, xlR1C1)

        'convert to a1 and make relative
        sFormula = Application.ConvertFormula(sFormula, xlR1C1, xlA1, , ActiveCell.Offset(ActiveCell.Row - .Cells(1).Row))

        With .FormatConditions.Add(Type:=xlExpression, _
                                 Formula1:=sFormula)

            .Interior.ColorIndex = 4
        End With

    End With

End Sub

我只按行偏移.Cells(1),因为在此示例中该列是绝对的。如果 CF 公式中的行和列都是相对的,则需要更多的偏移量。此外,这仅在活动单元格低于您范围内的第一个单元格时才有效。为了使其更通用,您必须确定活动单元格相对于范围的位置并适当地偏移。如果偏移量高于第 ​​1 行,则需要对其进行编码,以便它引用更接近您的 Excel 版本的总行数底部的单元格。

如果您认为选择有点麻烦,我相信您会同意这更糟。尽管我讨厌不必要的选择和激活,但条件格式和数据验证是必要的两个地方。

【讨论】:

    【解决方案3】:

    一个简单的例子:

    Sub Format_Range()
    
    Dim oRange          As Range
    Dim iRange_Rows     As Integer
    Dim iCnt            As Integer
    
    
    'First, create a named range manually in Excel (eg. "FORMAT_RANGE")
    'In your case that would be range "$A$5:$H$25". 
    'You only need to do this once, 
    'through VBA you can afterwards dynamically adapt size + location at any time. 
    
    'If you don't feel comfortable with that, you can create headers 
    'and look for the headers dynamically in the sheet to retrieve 
    'their position dynamically too. 
    
    'Setting this range makes it independent
    'from which sheet in the workbook is active
    'No unnecessary .Activate is needed and certainly no hard coded "A1" cell. 
    '(which makes it more potentially subject to bugs later on) 
    Set oRange = ThisWorkbook.Names("FORMAT_RANGE").RefersToRange
    iRange_Rows = oRange.Rows.Count
    
    For iCnt = 1 To iRange_Rows
        If oRange(iCnt, 1) <> oRange(iCnt, 2) Then
            oRange(iCnt, 2).Interior.ColorIndex = 4
        End If
    Next iCnt
    
    End Sub
    

    关于我在其他回复中给出的cmets:

    如果您必须对多行执行此操作,则将整个范围加载到内存(数组)并检查数组中的条件肯定会更快,然后在需要写入的单元格上进行写入书面(格式化)。
    我可以同意这种技术在这种情况下不是“必要的” - 但是这是一种很好的做法,因为它对于之后的许多(任何类型的)自定义都是灵活的,并且更容易调试(使用即时/本地/监视窗口)。
    我不是 Offset 的粉丝,尽管我没有说它不能正常工作,并且在某些有限的情况下,我可以说出现问题的机会“可能”很小:我经历过一些商业用户倾向于使用它不断地(这里偏移+3,那里偏移-3,然后再-2,等等......);虽然写起来很容易,但我可以告诉你,修改是地狱。当最终用户进行更改时,它也经常会出现错误。
    我非常“赞成”使用标题(尽管我也喜欢减少 Excel 的数据库功能,因为对于许多人来说,这会导致避免访问),因为它会给您带来很大的灵活性。即使我使用了第 1 列和第 2 列;更好的是根据标题的命名范围的位置动态检索列 nr。如果然后插入另一列,则不会出现错误。

    最后但并非最不重要的一点,这听起来可能有些夸张,但最后一次,我使用了一个具有属性和函数的类模块来动态执行对每张表中潜在数据的所有检索,对我能想到的所有错误和一些错误进行检查执行特定任务的附加功能。
    因此,如果您需要来自特定工作表的多种类型的数据,您可以实例化该类并让所有数据可供您使用,并可通过定义的函数访问。到目前为止,我还没有注意到有人这样做,但是尽管需要做更多的工作,但它给你带来的麻烦很少(你可以一遍又一遍地使用相同的原则)。
    现在我认为这不是您所需要的;但是有一天,您可能需要为不知道它是如何工作的最终用户制作大型工具,但会因为他们可能自己做过的事情而抱怨很多事情(即使这不是您的“错”);记住这一点很好。

    【讨论】:

    • 小心使用整数来计算行数。考虑使用 Long 代替,因为行数可能超过 Integer 可以容纳的数量。
    • 同意,在 Access 中我也使用 Longs。如果我知道会有很多“记录”,那么在 Excel 中也是如此。
    • @DickKusleika - 感谢您在回答中的精彩解释! KimGysen,感谢您提供此技术演示,但此解决方案不适合我的问题。如前所述,我也使用 NamedRanges 和 ArrayCopies,但这里的任务需要不同的方法,尽管我现在在 ConditionalFormula 中使用 NamedRange。我将在我的问题中为此添加一个解释。但是,您的循环构造是我的 LBOUND/UBOUND 方法的一个有趣的替代方案,谢谢!
    猜你喜欢
    • 2020-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-28
    • 2021-10-20
    相关资源
    最近更新 更多