【问题标题】:Odd issue with custom Excel VBA resetting values自定义 Excel VBA 重置值的奇怪问题
【发布时间】:2019-02-04 20:14:14
【问题描述】:

我正在同时做几件我认为可能会导致问题的事情。我已经在 Windows 10 下的 Office 2013 和 Office 2016 中测试了这个 VBA。

我有许多工作表,每个工作表的标题都基于月份和年份(例如:“2018 年 11 月”、“2018 年 12 月”等)。我正在使用 VBA 做两件(单独的)事情:

  1. 获取活动工作表的名称
  2. 遍历前一个工作表的数据

VBA 代码:

Public Function RelSheet(iPos As Integer, zRange As String)

    'Relative Worksheet Reference Facility
    'eg: =RelSheet(-1,"A3") = Cell A3 in Previous (Left) WSheet
    'eg: =RelSheet(1,"A3") = Cell A3 in Next (Right) WSheet
    'eg: "#Error" when reference does not exist
    'eg: Can do maths =RelSheet(1,"A3")*2

    Dim shtActive As Worksheet
    Application.Volatile True
    Set shtActive = Application.Caller.Worksheet
    On Error GoTo BadSheetReference
    RelSheet = Sheets(shtActive.Index + iPos).Range(zRange).Value

    GoTo ExitFunction

BadSheetReference:
    RelSheet = "#Error"

ExitFunction:
End Function

Function TabName()
  TabName = ActiveSheet.Name
End Function

在我的工作表中,我逐月统计总和,直到创建近一年,然后再次从 0(或 Jannuary 包含的任何值)开始统计。单元格 C8 是当月的值,单元格 C9 是上个月的值(上一个工作表的 C9)+ C8 中当前工作表单元格的值的总和。该单元格 (C9) 的公式如下: ==IF(ISNUMBER(SEARCH("January", TabName())), C8, RelSheet(-1, "C9")+C8)

不幸的是,一旦工作表名称​​确实包含“January”的文本,所有以前的工作表也会恢复为总和 0。我相信它与 RelSheet 函数有关,本质上是递归检查本身,但是当我在纸上逻辑地单步执行代码时,我看不出它是如何做到的。我目前的解决方法是手动将 1 月份工作表中特定单元格的值设置为 0,然后在后续电子表格中继续该公式。

【问题讨论】:

  • 默认情况下Sheets() 将引用活动工作簿 - 您是否打开了多个工作簿? TabName = ActiveSheet.Name 也可能是非活动工作表的问题
  • Sheets 是一个可枚举的,并且不能保证索引以任何方式排序。迭代Worksheets 集合并检查名称会更好。如果用户重新订购床单会怎样?
  • @Comintern - 你确定吗?我从来没有遇到过(例如)Sheets(2) 没有引用工作簿中的第二个选项卡的情况(假设至少有两个选项卡并且没有隐藏的选项卡)
  • 注意“活动工作表”在这里具有误导性 - 它不是 ActiveSheet,而是调用 UDF 的任何工作表:该工作表是否“活动”没有区别。我建议将shtActive 重命名为例如callerSheet 和不合格的 Sheets 集合,正如 Tim 在上面正确警告的那样,隐含地引用了 ActiveWorkbook 是什么,这意味着 UDF 将根据预期的书当前是否处于活动状态进行不同的评估。请改用ThisWorkbook.Worksheets,以保证确定性结果。
  • 没有 - 但Sheets 集合包含非工作表;当你知道你得到一个Worksheet 对象时使用Worksheets 集合(而不是,比如说,一个Chart 表)。重要的部分是 ThisWorkbook 限定符,没有它,您获得的工作表取决于当前处于活动状态的工作簿,或者(在其他情况下)甚至 VBA 项目中的哪个位置编写代码:相同的代码在标准模块中编写的行为可能与在工作表模块中编写时的行为方式不同,除非它使用 Workbook 对象引用正确/明确地限定。

标签: excel vba


【解决方案1】:

几个建议:

Public Function RelSheet(iPos As Integer, zRange As String)

    'Relative Worksheet Reference Facility
    'eg: =RelSheet(-1,"A3") = Cell A3 in Previous (Left) WSheet
    'eg: =RelSheet(1,"A3") = Cell A3 in Next (Right) WSheet
    'eg: "#Error" when reference does not exist
    'eg: Can do maths =RelSheet(1,"A3")*2

    Dim shtActive As Worksheet
    Application.Volatile True
    Set shtActive = Application.Caller.Worksheet
    On Error GoTo BadSheetReference
    '##added workbook qualifier
    RelSheet = ThisWorkbook.Sheets(shtActive.Index + iPos).Range(zRange).Value

    GoTo ExitFunction

BadSheetReference:
    RelSheet = CVErr(xlErrRef)

ExitFunction:
End Function

Function TabName()
  '## not ActiveSheet
  TabName = Application.Caller.Parent.Name
End Function

【讨论】:

  • 赞成,尽管我强烈建议返回 CVErr(xlErrRef)(实际的 #REF! 工作表错误值),而不是表示 #Error 并且 Excel 不理解为工作表错误的字符串价值。这将使 UDF 与 IFERRORISERROR 函数一起工作。
  • 我还建议shtActive.Parent 而不是ThisWorkbook 让它更通用一点。并更改变量名称以避免它在活动表上工作的误导性推断(例如shCaller
  • 太棒了。希望我能更好地了解/理解 VBA 的对象层次结构以了解我的绊脚石。我将不得不阅读 Sheets 和 ThisWorkbook.Sheets 以及 ActiveSheet 与 Application.Caller.Parent 之间的区别。非常感谢您在这里的专业知识(每个人!)!
  • 我在写这些函数的时候才意识到哦,很久以前,我已经查找并使用了Application.Caller...dang。只是证明你不使用它就会失去它。
猜你喜欢
  • 1970-01-01
  • 2021-07-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-11
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 2015-12-13
相关资源
最近更新 更多