首页 > 解决方案 > VBA 代码在一台计算机上运行,​​但在另一台计算机上出现语法错误

问题描述

我记录了下面的宏,它在我的电脑上运行良好(excel 2016)。但是,当我在另一台使用 excel 2013 的计算机上运行它时,它给了我“语法错误”并突出显示计算列 V“状态”的公式。它似乎也跳过了之前的所有内容,这意味着不添加 2 个新列并且不计算列 U“日期”。关于如何解决这个问题的任何想法?我不知道 2 个 excel 版本之间的语法差异。谢谢!

 Sub ReportingStatus()

    Dim LastRow As Range

    'Add 2 columes and format them
    Range("U6").Select
        ActiveCell.FormulaR1C1 = "Dates"
        Range("V6").Select
        ActiveCell.FormulaR1C1 = "Status"

        Range("T6").Select
        Application.CutCopyMode = False
        Selection.Copy
        Range("U6:V6").Select
        Selection.PasteSpecial Paste:=xlPasteFormats, Operation:=xlNone, _
            SkipBlanks:=False, Transpose:=False
        Application.CutCopyMode = False

        Range("S65536").End(xlUp).Select
        ActiveCell.Offset(0, 2).Select
        Set LastRow = ActiveCell

    'Calculate the value for column U "Date"
        Application.ScreenUpdating = False
        Range("U7", LastRow).Select

        Selection.FormulaR1C1 = _
            "=IF(RC[-2]=""Awaiting Management Response"",R2C1-RC[-9],IF(RC[-3]<>"""",MAX(RC[-3]-RC[-4],R2C1-RC[-3]),R2C1-RC[-4]))"

        LastRow.Offset(0, 1).Select

    'Calculate the value for column V "Status"
        Range("V7", ActiveCell).Select
        Selection.FormulaR1C1 = _
            "=IF(RC[-3]=""Awaiting Management Response"",IF(RC[-1]<1,""MGMT-CURRENT"",IF(AND(1<=RC[-1],RC[-1]<=60),""MGMT-DELAYED"",IF(AND(61<=RC[-1],RC[-1]<=90),""MGMT-SIGNIFICANTLY DELAYED"",""MGMT-CRITICAL""))),IF(RC[-1]<1,""CURRENT"",IF(AND(1<=RC[-1],RC[-1]<=60),""DELAYED"",IF(AND(61<=RC[-1],RC[-1]<=90),""SIGNIFICANTLY DELAYED"",""CRITICAL""))))"
        Range("V7").Select

        Columns("U:V").EntireColumn.AutoFit

标签: excelvba

解决方案


我没有足够的测试数据,也无法访问 Excel 2013,因此我无法复制您的语法错误,也无法检查我的宏是否会执行您想要的操作。

从您使用宏记录器生成的代码来看,我怀疑您对 VBA 很陌生。这个答案有几个部分。在每个部分中,我都试图解释一些你不知道或似乎误解的东西。这里有很多信息。我建议您慢慢阅读并尝试理解每个段落。如果您无法理解某个段落,请继续前进,稍后再回来。如果您想成为 VBA 程序员,我相信您最终必须了解这里的所有内容。但是您今天不必全部了解。

我怀疑问题出在第 V 列的冗长而复杂的公式上。我记得对于 Excel 2003,公式的最大长度为 255 个字符。嵌套也有限制。更高版本允许更长的公式和更多的嵌套。有人建议某些版本的 VBA 编译器会拒绝 Excel 不会拒绝的公式。在我的宏中,我已将您的长公式分解为更短且更易于理解的部分。

是什么给出了错误:编辑器或编译器或解释器

您抱怨:“它似乎也跳过了之前的所有内容,这意味着不添加 2 个新列并且不计算列 U“日期””。由此,我推断您不了解各个阶段以及何时检查什么。

编辑器会在您键入源代码时进行检查。写If A=1,编辑会立即告诉你,If没有Then.

如果单击 [Debug] 然后单击 [CompileVBAProject],您将编译每个模块中的每个宏。如果单击 [Run] 或 [F5],您正在为当前宏运行编译器,如果编译器没有发现错误,它将启动解释器。

除非发现错误,否则编译器会将源代码转换为立即代码。也就是说,编译器将人类易于阅读的内容转换为解释器易于阅读的内容。编译器可以发现诸如“变量未定义”或“变量定义两次”之类的错误。

解释器是运行 VBA 宏的程序。如果您编写类似Cells(RowCrnt, ColCrnt).Value = 1的内容,则解释器会注意到 的当前值为RowCrnt0 并报告没有这样的行。

如果解释器在尝试创建 V 列时发现错误,则宏的早期部分将被遵循,并且您将在 U 列中获得新的列标题和公式。

如果编辑器发现错误,则在您完成一行时会显示该消息。如果您接受消息并离开线路,它会变为红色。

如果您单击[运行]或[F5]并且在报告错误之前似乎没有发生任何事情,那将是因为编译器发现了错误。与 [Run] 或 [F5] 有点混淆,在解释器启动之前只编译了第一个宏。如果第一个宏调用第二个宏,则仅在调用第二个宏时对其进行编译。如果第二个宏包含错误,则会报告它。第一个宏中的语句直到第二个宏的调用都将被执行,但第二个宏中的语句不会被执行。

有时错误消息是完全清楚的,并且您知道如何更正它。在其他情况下,识别报告错误的内容可能非常有帮助。

宏记录器

宏记录器不知道您要做什么。它逐句记录您正在做的事情,而对您要达到的目标没有任何理解。例如:

您将光标移动到单元格 U6 您输入了“日期” 您将光标移动到单元格 V6 您输入了“状态”</p>

宏记录器将其记录为:

Range("U6").Select
ActiveCell.FormulaR1C1 = "Dates"
Range("V6").Select
ActiveCell.FormulaR1C1 = "Status"

这是语法正确的 VBA,但它是糟糕的 VBA。选择一个单元格是一个缓慢的命令,因为解释器必须重新绘制屏幕以使所选单元格可见。Application.ScreenUpdating = False您可以通过在宏顶部添加来加快选择单元格,但它仍然会很慢。

程序员会写:

Range("U6").Value = "Dates"
Range("V6").Value = "Status"

因为程序员知道他们不需要移动光标。

实际上,一个好的程序员不会这样写。此代码仅适用于特定工作表。假设工作表被命名为“Tasks”,一个好的程序员会这样写:

With Worksheets("Tasks")
  .Range("U6").Value = "Dates"
  .Range("V6").Value = "Status"
End With

如果您切换工作表,宏记录器将记录Worksheets("Data").Activate,但不会记录开始记录时哪个工作表处于活动状态。

添加WithandEnd With语句并在 Range 前添加句点有两个好处。(1) 如果启动宏时激活了错误的工作表,它仍然可以工作。如果没有宏,With您的宏会写在其他工作表上,并且 UnDo 命令不会撤消宏所做的事情。(2) 当您或其他人需要在六个月或十二个月内更新此宏时,将清楚哪个工作表是此代码的目标。

接下来你有:

Range("T6").Select
Application.CutCopyMode = False
Selection.Copy
Range("U6:V6").Select
Selection.PasteSpecial Paste:=xlPasteFormats, Operation:=xlNone, _
  SkipBlanks:=False, Transpose:=False
  Application.CutCopyMode = False

我花了一些时间来弄清楚这段代码实现了什么。您正在将格式从标题的前一个单元格复制到新单元格。再次缓慢的命令虽然作为一次性的可能无关紧要。更重要的是,当有人必须在十二个月内更新此宏时,他们将花费多少时间来破译此代码?

有几种方法可以达到相同的效果。我想我会去:

With Worksheets("Tasks")
   .Range("T6").Copy Destination:=.Range("U6:V6")  ‘ Copy everything but only need formats
  .Range("U6").Value = "Dates"
  .Range("V6").Value = "Status"
End With

在新的声明中,Destination:=是可选的,但我总是包含它,因为我认为它清楚地表明了该声明的作用。该注释将帮助未来的维护程序员理解该语句的目的。没有评论,看起来好像我正在从单元格 T6 复制值,因为不是每个人都知道Copy复制格式。

下一个命令是:

Range("S65536").End(xlUp).Select

这句话让我很困扰。65,536 是 Excel 2003 的最后一行。从那时起,最后一行是 1,048,576。这不是宏记录器将记录的内容。您是否输入了此语句?如果是这样,为什么是“S65536”?您是否尝试使用 Excel 2016 维护 Excel 2003 工作簿?

您说您同时使用 Excel 2016 和 Excel 2013。您是否也在使用 Excel 2003?如果您使用多个版本的 Excel,则需要非常小心。Microsoft 努力使他们的产品向后和向前兼容,但这确实意味着 100% 兼容。较新版本具有在旧版本中不起作用的功能。Excel 2007 是对 Excel 2003 的完全重写,并且在不兼容时有报告。这些不兼容性显然在 Excel 2010 中得到了修复,这意味着 Excel 2010 与 Excel 2003 的兼容性高于与 Excel 2007 的兼容性。您不能假设为 Excel 2016 编写的宏适用于早期版本;它可能会,但您必须完全测试这些宏。

识别最后一行时的完整块是:

.Range("S65536").End(xlUp).Select
ActiveCell.Offset(0, 2).Select
Set LastRow = ActiveCell

LastRow不是最后一行,而是 U 列中的最后一个单元格。我见过一些由误导性名称引起的灾难。维护程序员假设变量的名称正确地识别了它包含的内容,并在此假设下编写新代码。

我从来没有为一个 Excel 版本编写过宏来维护另一个版本的工作簿,所以如果你正在做的是,你需要测试以下内容。

要获得活动工作表S 列中的最大行数,请编写:

LastRow = Cells(Rows.Count, "S").End(xlUp).Row

要获取活动With语句中命名的工作表的 S 列中的最大行,请编写:

LastRow = .Cells(.Rows.Count, "S").End(xlUp).Row

Rows.Count返回工作表中的行数。我从未编写过与哪个工作簿相关的宏,因为所有打开的工作簿的所有工作表都具有相同的行数。如果您同时访问 Excel 2003 和更高版本的工作簿,则存在不同大小的工作表,您需要仔细检查是否获得了所需的计数。

请注意Range("Xn"),我写的是 ,而不是Cells(RowNumber, ColumnId)。这只是识别细胞的另一种方式。RowNumber 必须是整数或整数表达式。ColumnId 可以是字母(例如“S”),也可以替换为 ColumnNumber、整数(例如 20)或整数表达式。

如果您正在访问列中的任何单元格:

For ColCrnt = 1 to 20
  "    "    "
Next

你真的需要Cells(RowNumber, ColumnNumber) 我喜欢保持一致,所以我没有Range一个陈述和Cells下一个陈述。Cells每当我引用单个单元格和Range(Cells(TopRow, LeftColumn) , Cells(BottomRow, RightColumn))矩形时,我总是使用它。这不是必需的,但我发现一致性会随着时间的推移而有所回报。

Application.ScreenUpdating = False应该在宏的顶部。

我更换了:

'Calculate the value for column U "Date"
Range("U7", LastRow).Select
Selection.FormulaR1C1 = _
        "=IF(RC[-2]=""Awaiting Management Response"",R2C1-RC[-9],IF(RC[-3]<>"""",MAX(RC[-3]-RC[-4],R2C1-RC[-3]),R2C1-RC[-4]))"

经过

'Set all used cells in column U ("Date") to the required formula
.Range(.Cells(7, "U"), .Cells(LastRow, "U")).Value = _
         "=IF(RC[-2]=""Awaiting Management Response"",R2C1-RC[-9],IF(RC[-3]<>""""" & _
         ",MAX(RC[-3]-RC[-4],R2C1-RC[-3]),R2C1-RC[-4]))"

我使用了Range(Cells(TopRow, LeftColumn) , Cells(BottomRow, RightColumn))上面提到的语法。我已经拆分了长文字,因为我不喜欢从屏幕右侧消失的语句。

我为第五列做了类似的事情。在处理第五列时,我发现了一个可能的问题。单元格 V7 的公式长度为 276 个字符。这种长度的公式对于 Excel 2003 是无效的,尽管我能找到的所有文档都表明它对所有更高版本都有效。如果您使用 Excel 2003,这可能是个问题,尽管我不明白为什么它会在编译时被发现。话虽如此,有报道称行的长度有限制,这将是编译时错误。您可以尝试缩写字符串。因此,例如,“MSigDel”而不是“MGMT-显着延迟”</p>

另一种方法是拆分公式。如果有必要修改它,这不是一个容易理解的公式。拆分它会更容易理解和修改。在下面的最终代码中,我拆分了公式。我没有测试数据,所以我不能保证正确拆分它,但我想我有。

常数

逐行处理您的代码后,我本可以在这里停止修改您的宏。修复由宏记录器创建的糟糕的 VBA 改进了宏,但如果我有您的要求,它仍然不是我会编写的宏。

我永远不会像你那样在我的代码中写 6、A2、L、Q、R、U 或 V。目前,您的列标题位于第 6 行,但这可能会改变。如果添加了额外的列,则 U 和 V 列将不得不移动。查看所有 6 的代码,Us 和 Vs 是一种痛苦,很容易错过。更糟糕的是,如果交换了两列,那么所有的 E 都变成了 F,反之亦然。使用常量要好得多:

Const ColLastExist As String = "T"    ' The last column in the main report
Const ColFrstNew As String = "U"      ' The first status column
Const ColLastNew As String = "V"      ' The last status column
Const RowHead As Long = 6             ' Row containing column headers

您想从最后一个现有列中移动格式。这当前是 T 列,但添加新列可能会改变这一点。目前,第一个新列是 U,最后一个是 V。如果在主报表中添加另一列,它们将移动。也有可能是一个额外的新列。

移动格式的声明从:

.Range("T6").Copy Destination:=.Range("U6:V6")  ‘ Copy everything but only need formats

至:

' Copy everything from last existing column head but only need formats
.Cells(RowHead, ColLastExist).Copy Destination:= _
         .Range(Cells(RowHead, ColFrstNew), Cells(RowHead, ColLastNew))

第二个版本肯定有更多的打字。但是第一个版本是什么意思?假设您在十二个月内查看此内容,因为需要进行一些更改;也许延迟、显着延迟和关键的范围已经改变。你还记得T6、U6和V6是什么吗?在第二个版本中,行和列被命名。语法对您来说可能有点奇怪,但请记住,我总是对单元格和矩形使用相同的语法。如果你这样做,你很快就会对语法非常熟悉。更重要的是,如果标题行、主报表的最后一列和/或新的状态列发生了变化,您需要做的就是更新常量。

现在考虑:

Const ColDates As String = "U"        ' The Dates column for the status columns
Const ColSts As String = "V"          ' The column for the status string

' Set headers for new status columns
.Cells(RowHead, ColDates).Value = "Dates"
.Cells(RowHead, ColSts).Value = "Status"

取代:

.Range("U6").Value = "Dates"
.Range("V6").Value = "Status"

请注意,我已将“U”定义为 ColDates 和 ColFrstNew。当我想将新列作为一个范围引用时,这给了我一个名称,当我想单独引用它们时,这给了我另一个名称。我试图在十二个月内让自己和维护程序员的事情变得简单。我想让每个常量的含义尽可能的明显。

一口气可以吸收很多东西。直到几个月后您再次查看此宏时,常量的值才会显而易见。或者您可能会编写另一个没有常量的宏;如果您必须更新该宏,您将欣赏它们的价值。

您还使用列 L、Q、R 和 S。您使用单元格 A2。我不知道这些列的用途,所以没有尝试命名它们。

A1 与 R1C1 格式

您已将 R1C1 格式用于日期列的公式。我不喜欢这种格式。您如何轻松计算出 RC[-9] 引用的单元格。我为状态列编写的新公式使用了 A1 格式。

新宏

下面是我的宏版本加上我的两个标准宏。它可以完全替代您的模块。

我写 VBA 宏已经 16 年了。同样的问题会重复出现很多次,所以我编写了一个宏来解决这个问题并将它们保存在 PERSONAL.XLSB 中。这两个标准宏将列号转换为列代码,反之亦然。

我已将您的工作表命名为“任务”...见第 29 行。将我的名字改为您的。否则,此代码无需修改即可工作。我没有适当的测试数据,但我认为我已经正确地用较短的公式替换了你的长公式。

必要时回来提出问题,但是您研究我的代码并尝试弄清楚我为什么要编写它的原因越多,您的 VBA 编程技能就会发展得越快。

Option Explicit
Sub ReportingStatus()

  ' Add new columns to worksheet "Tasks" starting at ColFrstNew.
  ' The first new column is a number.  A postive value indicates the task reported on the row is late
  ' The second new column is a string indicating the status of the task from Current to Critical.
  ' The remaining new columns are used to build the string in the second column

  Const ClrWhite35 As Long = 10921638   ' Theme colour white, darker 35%

  Const ColLastExist As String = "T"    ' The last column in the main report
  Const ColDates As String = "U"        ' The Dates column for the status columns
  Const ColFrstNew As String = "U"      ' The first status column
  Const ColLastNew As String = "V"      ' The last status column
  Const ColSts As String = "V"          ' The column for the status string
  Const ColFrstNewBld As String = "W"   ' The first column used to build the status string
  Const ColLastNewBld As String = "AA"  ' The last column used to build the status string
  Const RowHead As Long = 6             ' Row containing column headers
  Const RowDataFirst As Long = 7        ' First data row

  Dim ColCrnt As Long                   ' For-loop variable for accessing build columns
  Dim Formulae As Variant               ' Set to an array of formulae
  Dim FormulaForSts As String           ' Used to build formula for status column
  Dim InxF As Long                      ' Index into array Formulae
  Dim RowLast As Long                   ' Last used row

  Application.ScreenUpdating = False

  With Worksheets("Tasks")

    ' Copy everything from last existing column head but only need formats
    ' Objective is to ensure new column headers look like the earlier ones
    .Cells(RowHead, ColLastExist).Copy Destination:= _
           .Range(Cells(RowHead, ColFrstNew), Cells(RowHead, ColLastNew))

    ' Set headers for new status columns.
    .Cells(RowHead, ColDates).Value = "Dates"
    .Cells(RowHead, ColSts).Value = "Status"

    ' Find last row of column S.  Formulae will be placed on
    ' rows RowDataFirst to RowLast
    RowLast = Cells(.Rows.Count, "S").End(xlUp).Row

    'For all used data rows, set cell in column "Dates" to the required formula
    .Range(.Cells(7, ColDates), .Cells(LastRow, ColDates)).Value = _
             "=IF(RC[-2]=""Awaiting Management Response"",R2C1-RC[-9],IF(RC[-3]<>""""" & _
             ",MAX(RC[-3]-RC[-4],R2C1-RC[-3]),R2C1-RC[-4]))"

    ' Load formulae to an array so they can be transferred to cells using a simple loop
    Formulae = VBA.Array("=IF(S7=""Awaiting Management Response"",""MGMT-"","""")", _
                         "=IF(U7<1,""CURRENT"","""")", _
                         "=IF(AND(1<=U7,U7<=60),""DELAYED"","""")", _
                         "=IF(AND(61<=U7,U7<=90),""SIGNIFICANTLY DELAYED"","""")", _
                         "=IF(U7>90,""CRITICAL"","""")")

    ' Value for status column is: "MGMT-" or "" followed by
    ' "CURRENT" or "DELAYED" or "SIGNIFICANTLY DELAYED" or "CRITICAL"
    ' The formulae above are placed in columns ColFrstNewBld to ColLastNewBld.
    ' There values are concatenated to create the status string

    InxF = 0             ' the lower bound of a VBA.Array if always zero.
    FormulaForSts = "="  ' Start formula for status column

    For ColCrnt = ColCodeToNum(ColFrstNewBld) To ColCodeToNum(ColLastNewBld)
      ' For all used data rows, set cells in build columns to partial
      ' strings required for column "Status"
      .Range(.Cells(RowDataFirst, ColCrnt), .Cells(LastRow, ColCrnt)).Value = Formulae(InxF)
      ' Concatenate A1 name for this cell to formula for status column
      FormulaForSts = FormulaForSts & ColNumToCode(ColCrnt) & RowDataFirst
      ' If this is not the last column, add concatenate operator to formula
      If ColCrnt < ColCodeToNum(ColLastNewBld) Then
        FormulaForSts = FormulaForSts & "&"
      End If
      InxF = InxF + 1   ' Advance to next formula
    Next

    ' The first build column contains "MGMT-" or ""
    ' The remaining columns contain one of "CURRENT" to "CRITICAL" depending
    ' on how late the task is.
    ' FormulaForSts concatenates the build columns into the required status
    .Range(.Cells(RowDataFirst, ColSts), .Cells(LastRow, ColSts)).Value = FormulaForSts

    With .Columns(ColFrstNewBld & ":" & ColLastNewBld)
      .Font.Color = ClrWhite35   ' Use pale grey as font colour for build columns
    End With
    With .Columns(ColFrstNew & ":" & ColLastNew)
      .EntireColumn.AutoFit
    End With

  End With

End Sub
Public Function ColCodeToNum(ByVal ColCode As String) As Long

  ' Checks ColCode is a valid column code for the version of Excel in use.
  ' If it is, it returns the equivalent column number.
  ' If it is not, it returns 0.

  ' 21Aug16  Coded
  ' 28Oct16  Renamed ColCode to match ColNum.
  ' ???????  Renamed from ColNum to provide a more helpful name.

  Dim ChrCrnt As String
  Dim ColCodeUc As String:   ColCodeUc = UCase(ColCode)
  Dim Pos As Long

  ColCodeToNum = 0

  For Pos = 1 To Len(ColCodeUc)
    ChrCrnt = Mid(ColCodeUc, Pos, 1)
    If ChrCrnt < "A" Or ChrCrnt > "Z" Then
      Debug.Assert False   ' Invalid column code
      ColCodeToNum = 0
      Exit Function
    End If
    ColCodeToNum = ColCodeToNum * 26 + Asc(ChrCrnt) - 64
  Next

  If ColCodeToNum < 1 Or ColCodeToNum > Columns.Count Then
    Debug.Assert False   ' Invalid column code
    ColCodeToNum = 0
    ColCodeToNum = 0
  End If

End Function
Public Function ColNumToCode(ByVal ColNum As Long) As String

  Dim ColCode As String
  Dim PartNum As Long

  ' 3Feb12  Adapted to handle three character codes.
  ' ??????  Renamed from ColCode to create a more helpful name

  If ColNum = 0 Then
    ColNumToCode = "0"
  Else
    ColCode = ""
    Do While ColNum > 0
      PartNum = (ColNum - 1) Mod 26
      ColCode = Chr(65 + PartNum) & ColCode
      ColNum = (ColNum - PartNum - 1) \ 26
    Loop
  End If

  ColNumToCode = ColCode

End Function

推荐阅读