4

如果 Excel 中表的特定列(ListObject)是计算列(如http://office.microsoft.com/en-us/excel-help/use-calculated-columns-在-an-excel-table-HA010342380.aspx 中)?

请注意,计算列不仅会为每一行设置相同的 R1C1 公式,而且还会在添加新行时自动扩展(如果您删除整个数据主体范围然后重新创建一些新行,它甚至会重新填充)。因此,检查具有一致公式的列与检查计算公式不同。

甚至可以计算一列,但是用其他公式或值覆盖其中一行,并保持自动扩展功能。

所以我非常确信这必须是列的某些属性,我只是不确定我可以在哪里通过 VBA 访问它。如果它没有通过 VBA 对象模型公开,是否有一些解决方法来获取此信息?

提前致谢, 卡洛斯

编辑:我对 Excel Office Open XML 文件进行了一些挖掘,结果发现我正在寻找的是xl\tables\table*.xml 文件定义中的<calculatedColumnFormula>元素。<tableColumn>有什么方法可以通过 VBA 实现吗?

EDIT2:这是一个示例文件,其中包含我可以提出的测试用例。VBA 应该表示第 1、2 和 3 列是计算列,而第 4 和 5 列不是。

4

2 回答 2

4

以前没有看过这个,但它似乎是 ListObject 的列范围的一个属性,如下所示:

Dim wks As Worksheet
Set wks = ActiveSheet
Dim li As ListObject
Set li = wks.ListObjects(1)
Dim col As ListColumn
Set col = li.ListColumns(2)    ' assuming column 2 of the table has a calculated formula
Dim r As Range
Set r = col.DataBodyRange
Let b = Not IsNull(r.FormulaArray)
if b then
    Let b = Len(r.FormulaArray) > 0 ' case where r.FormulaArray = "", suspect it's not a calculated column
End If
MsgBox b

如果 IsNull(r.FormulaArray) 则它没有计算列,否则它有。

hth


好的,玩了一下,我看到使用上面获得的范围对象与任何给定单元格的范围对象不同,所以如果你有一个给定的单元格,我认为你需要获取相应的 ListColumn 的范围通过 .DataBodyRange。

(例如,如果您插入Set r = r.Cells(1,1)上述内容,则IsNull(r.FormulaArray)测试不再适用于测试计算列,而只是说明该范围是否有公式,但可以计算或不计算。)

此外,虽然 r.FormulaArray 在计算列时似乎是一个字符串,但如果不是(计算列),则 .FormulaArray 产生 null,这不是有效的字符串值(使其难以在要捕获您必须使用变体而不是布尔值的值);我发现 IsNull(r.FormulaArray) 似乎工作正常。


如果我在已计算的列的右侧添加一列,则 r.FormulaArray = "" 为该新添加的列。如果您将一个值放入其中一个单元格,则公式数组会立即恢复为更预期的 NULL。所以,我添加了一个测试,我认为这是误报。

于 2013-01-09T19:43:28.017 回答
2

这为您的示例提供了适当的答案。

不幸的是,它有一些潜在的致命缺点,这取决于一个人的情况。一方面,它会导致重新计算,因此通过=RAND()样本中的公式生成的随机数会发生变化。

第二个缺点是它修改工作表以获得答案(它删除了它所做的修改,但它仍然被修改)。我可以想到一些仅部分有用的解决方法:(a)根据需要很少执行此操作并缓存所有列的结果,以及(b)将表复制到新工作簿并运行例程(并删除新工作簿) . 虽然后者可以避免修改缺陷,但它仍然会触发对原始工作簿的重新计算(否则有其自身的缺陷)。除此之外,将表格复制到新工作簿会丢失表格/列表对象,除非您复制整个范围(不仅仅是标题);那么它似乎也将第 4 列(非计算一致的公式)提升为计算的列。可悲的是,复制整张纸时也会发生这种促销活动。

好吧,FWIW:

Sub TestTable()
    Dim ans As String
    Let ans = ""

    Dim li As ListObject
    Set li = ActiveSheet.ListObjects(1)

    Dim rowCountBefore As Long
    Let rowCountBefore = li.ListRows.Count

    Dim lr As ListRow
    Set lr = Nothing

    On Error Resume Next
    Set lr = li.ListRows.Add(AlwaysInsert:=True)
    On Error GoTo 0

    Dim rowCountAfter As Long
    Let rowCountAfter = li.ListRows.Count

    If Not (lr Is Nothing) And rowCountAfter = rowCountBefore + 1 Then
        Dim c As Long
        For c = 1 To li.DataBodyRange.Columns.Count
            Dim b As Boolean
            Let b = lr.Range.Cells(1, c).HasFormula
            ans = ans & "col " & c & ": " & b & ";  "
        Next
        li.ListRows(rowCountAfter).Delete
    End If

    MsgBox ans
End Sub
于 2013-01-10T18:02:42.113 回答