【问题标题】:Range.Address Truncated when Used in Formula (String Too Long)Range.Address 在公式中使用时被截断(字符串太长)
【发布时间】:2018-08-21 20:56:26
【问题描述】:

我有循环遍历用户选择范围的第 5 列的代码,如果第 5 列中的当前单元格符合条件,则将同一行中的下 10 个单元格添加到范围中。在所有内容的末尾,如果单元格 A1 为空,它将对单元格 A1 中的最终范围求和。

Sub SumItems()
   Dim selectedRange As Range
   Set selectedRange = Selection

   Dim sumRange As Range
   Dim i As Long
   For i = 1 To selectedRange.Rows.Count
      If selectedRange(i, 5) = "Yes" Then
         If sumRange Is Nothing Then
            Set sumRange = Range(selectedRange(i, 5).Offset(0, 1), selectedRange(i, 5).Offset(0, 10))
         Else
            Set sumRange = Union(sumRange, Range(selectedRange(i, 5).Offset(0, 1), selectedRange(i, 5).Offset(0, 10)))
         End If
      End If
   Next i

   If Not sumRange Is Nothing Then
      If IsEmpty(Range("A1")) Then
         Range("A1").Formula = "=SUM(" + sumRange.Address(False, False) + ")"
      Else
         MsgBox "Cannot paste in cell A1, it is not empty."
         Exit Sub
      End If
   Else
      MsgBox "No matching rows were found."
      Exit Sub
   End If
End Sub

我运行它的床单很长。所以 sumRange 通常是工作表中许多不同区域的联合。我注意到一旦 sumRange.Areas.Count 超过 18,我就会遇到 Range("A1").Formula 语句的问题。 sumRange.Address(False, False) 字符串在第 18 个区域后被截断。我相信这是因为 .Address 字符串长度太长,刚好与这张表的 18 个区域重合。

我开发了一个临时解决方案,但它限制了子例程可以处理的区域数量,并且笨重且冗长。如果 sumRange 有超过 18 个区域,我设置了临时范围变量以将其拆分为,并且 IF 语句现在读取如下内容:

If Not sumRange Is Nothing Then
   If IsEmpty(Range("A1")) Then
      If sumRange.Areas.Count > 18 Then
         Dim k As Long
         Dim tmpSumRange1 As Range
         Dim tmpSumRange2 As Range
         Dim tmpSumRange3 As Range
         ' And so on...
         For k = 1 To sumRange.Areas.Count
            If k <= 18 Then
               If tmpSumRange1 Is Nothing Then
                  Set tmpSumRange1 = sumRange.Areas(k)
               Else
                  Set tmpSumRange1 = Union(tmpSumRange1, sumRange.Areas(k))
               End If
            ElseIf k > 18 And k <= 36 Then
               If tmpSumRange2 Is Nothing Then
                  Set tmpSumRange2 = sumRange.Areas(k)
               Else
                  Set tmpSumRange2 = Union(tmpSumRange2, sumRange.Areas(k))
               End If
            ElseIf k > 36 And k <= 54 Then
               If tmpSumRange3 Is Nothing Then
                  Set tmpSumRange3 = sumRange.Areas(k)
               Else
                  Set tmpSumRange3 = Union(tmpSumRange3, sumRange.Areas(k))
               End If

            ElseIf
               ' And so on until I get to k = 180...
            End If
         Next k

         If Not tmpSumRange1 Is Nothing And tmpSumRange2 Is Nothing And tmpSumRange3 Is Nothing Then
            Range("A1").Formula = "=SUM(" + tmpSumRange1.Address(False, False) + ")"
         ElseIf Not tmpSumRange1 Is Nothing And Not tmpSumRange2 Is Nothing And tmpSumRange3 Is Nothing Then
            Range("A1").Formula = "=SUM(" + tmpSumRange1.Address(False, False) + "," + tmpSumRange2.Address(False, False) + ")"
         ElseIf Not tmpSumRange1 Is Nothing And tmpSumRange2 Is Nothing And Not tmpSumRange3 Is Nothing Then
            Range("A1").Formula = "=SUM(" + tmpSumRange1.Address(False, False) + "," + tmpSumRange2.Address(False, False) + "," + tmpSumRange3.Address(False, False) + ")"
         ElseIf
            ' And so on all the way up to tmpSumRange10...
         End If
      Else
         Range("A1").Formula = "=SUM(" + sumRange.Address(False, False) + ")"
      End If
   Else
      MsgBox "Cannot paste in cell A1, it is not empty."
      Exit Sub
   End If
Else
   MsgBox "No matching rows were found."
   Exit Sub
End If

我知道肯定有比这更好的解决方案。我在搜索中没有找到任何东西。我也没有找到遇到同样问题的人。我需要 A1 单元格中包含 SUM 公式以及其中的范围,否则我只需对子例程中的值求和并插入值而不是公式。

【问题讨论】:

  • 你能举例说明这些地址是什么样的吗?因为我认为限制不是由区域数量强加的,而是由地址的字符串长度强加的。如果是这样,我很确定这里还有其他相关的解决方案来处理“非常长”的公式评估。
  • 我认为你在正确的轨道上。但是,不要硬编码三个临时变量,而是将其作为表示范围的集合或数组来执行,并且您应该始终检查 255 个字符的限制,而不是区域的大小,这样它就更灵活,更不容易出错如果工作表/工作簿的结构发生变化。
  • 啊,当我这样做时,它抱怨太多争论。我刚刚查了一下,Excel 函数的最大参数限制为 255 个(嗯,这个数字似乎很熟悉)。所以看起来我将不得不检查有多少参数和错误,如果它大于 255?
  • 取决于您想要使用它的复杂程度。当然,您可以在工作表中添加一堆Names,每个都可以由 255 个单独的范围定义。但我认为一般来说,如果你遇到这个问题,考虑彻底检查你的应用程序是个好主意。你不应该(通常)处理复杂的公式,如果你是,也许你只需要在 VBA 中进行评估,并从工作表上的按钮/等处按需获取它,而不是公式。
  • 感谢您的所有帮助,大卫。我很感激。主要的是我需要公式可以粘贴到其他纸张上并可以重复使用。我现在正在经历绝对最坏的情况,所以我怀疑我是否会有一个包含 255 个参数的公式。很高兴知道它的局限性,所以我会弄清楚如何检查我提供的公式的参数数量,如果它有太多错误就会出错。

标签: excel vba subroutine


【解决方案1】:

所以有两个独立但可能相关的问题:

  • Range.Address 最多截断 255 个字符,没有错误。这很奇怪,因为虽然 Excel 足够聪明,不会给您留下会引发 1004 的部分地址字符串,但同时它也很愚蠢,允许截断 Address,如果在公式等
  • 如果字符串通过ByVal(参见this old thread)进行讨论,则 Excel 函数的字符串参数有 255 个字符的限制,但我摘录了相关位,如下)。

Excel 函数参数将接受长度不超过 32767 的字符串的引用(可能的总长度为 32k 或 2^15)。 ...但是如果我们传递一个值而不是一个引用呢?

我们可以将 reference 传递给最大为 32k 的字符串,我们只能传递一个字符串 value 最大为 255

问题似乎是 Formula 长度的结果,但这是一个红鲱鱼。

您肯定会遇到Range.Address 的截断问题。

问题:Range.Address 在 255 个字符处截断

因为这种情况是静默发生的,所以您基于截断地址构建的任何公式都会受到不利影响;结果不会是您所期望的,因为公式不会代表范围的所有部分!

我已将此记录为 Microsoft 的错误,以防您想关注该问题:

https://github.com/MicrosoftDocs/VBA-Docs/issues/49

同时,幸运的是,解决方案非常简单(虽然实现起来并不简单!),看看你是否能适应你的目的,但想法是:

  1. 创建一个System.Collections.ArrayList,我们将在其中存储所有单独的Area 地址(或者您可以使用Variant,但使用ReDim 等会有些笨拙)
  2. 使用 ToArray() 方法将 ArrayList 转换为变体数组
  3. Formula 字符串中使用Join 函数的结果

Sub example()

Dim myNames As Object
Dim formula as String
Dim k As Long
Dim sumRange As Range
Dim thisArea As Range

' Example:
'   -- creates a range consisting of 100 Areas
'   -- this .Address will truncate to 252 characters!
For k = 1 To 200 Step 2
    Set thisArea = Range("A" & k)
    If sumRange Is Nothing Then Set sumRange = thisArea
    Set sumRange = Union(sumRange, thisArea)
Next

' Iterate the sumRange union and add each Area to an ArrayList
Set myNames = CreateObject("System.Collections.ArrayList")
For Each thisArea In sumRange.Areas
    myNames.Add thisArea.Address
Next

' Output to worksheet:
Dim outputCell As Range
Set outputCell = Range("F1")
formula = "=Sum(" & Join(myNames.ToArray(), ",") & ")"
outputCell.Formula = formula

End Sub

您可以在工作表中看到超长的公式 (Len == 650):

如果您不习惯使用 ArrayList,您可以将其强制为 `String:

' Iterate the sumRange union and add each Area to our formula string
formula = "=Sum("
For Each thisArea In sumRange.Areas
    formula = formula & thisArea.Address & ","
Next
formula = Left(formula, Len(formula) - 1)
formula = formula & ")"

【讨论】:

  • 这很好,感谢您的帮助。这比我的解决方案干净得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-17
  • 2015-07-10
相关资源
最近更新 更多