【问题标题】:loop through specified sheets in VBA循环遍历 VBA 中的指定工作表
【发布时间】:2015-03-11 10:15:18
【问题描述】:

我正在尝试使用我在这里找到的一些代码 For Each Function, to loop through specifically named worksheets 循环遍历工作簿中的指定工作表,运行少量代码并移至下一个工作表。

Sub LoopThroughSheets()
Dim Assets As Worksheet
Dim Asset As Worksheet


Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
For Each Asset In Assets
'my code here
   MsgBox ActiveSheet.Name 'test loop

Next Asset

End Sub

这不是循环通过工作表。我试过Dim Assets as Worksheet,但这破坏了代码。

非常感谢任何帮助,

干杯

【问题讨论】:

    标签: vba loops excel


    【解决方案1】:

    使用变体代替工作表。

    Array 返回一个 Variant 字符串数组,因此不能转换为 WorksheetEach 变量必须是 Variant

    Dim Assets As Variant
    Dim Asset  As Variant
    
    Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
    
    For Each Asset In Assets
        'my code here
        Sheets(Asset).Select
        MsgBox ActiveSheet.Name 'test loop
    Next Asset
    

    【讨论】:

    • Alex,在这个例子中使用 Variants 而不是 Strings 在内存分配方面有什么区别?我很好奇。
    • 谢谢亚历克斯,当我使用变体时,它似乎没有循环通过,因为消息框每次都给出相同的工作表名称
    • A variant::string 是大小 + 大约 20 字节的开销,所以它完全是微不足道的
    • 请不要鼓励初学者使用Select
    • 除了选择,您还有什么建议?
    【解决方案2】:

    解决了,但总是很高兴听到其他方法

    Sub loopsheets()
    
    Dim Sh As Worksheet
    For Each Sh In Sheets(Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables"))
    
    MsgBox Sh.Range("b1")
    
    Next
    End Sub
    

    干杯

    【讨论】:

      【解决方案3】:

      您在问题中显示的代码失败,原因是:

      Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
      

      Assets 是一个工作表,它是一种对象,在为对象赋值时必须使用Set

      Set Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
      

      这会失败,因为Array("…") 不是工作表。

      您暗示您的代码的早期版本会运行,但不会在工作表中循环。原因是:

      MsgBox ActiveSheet.Name
      

      这会显示活动工作表的名称,但此循环中的任何内容都不会更改活动工作表。

      我对您的解决方案不满意,尽管它没有明显的错误。我见过太多的程序失败,因为程序员在一个语句中做了太多。首先,越复杂的语句,一开始就做对的时间就越长,在后续的维护过程中也需要越长的时间来理解。有时,最初的程序员会稍微错误地表述;有时维护程序员在尝试更新它时会出错。在任何情况下,程序员花费的额外时间都不能证明运行时的任何节省是合理的。

      Alex K 已修复您的代码,方法是根据 VBA 的要求将 AssetsAsset 重新定义为 Variants,并添加 Sheets(Asset).Select 以更改哪个工作表处于活动状态。我不能同意这一点,因为Select 是一个缓慢的声明。特别是,如果您不包含 Application.ScreenUpdating = False,则您的例程持续时间可能会随着从每个 Select 重新绘制屏幕而达到顶峰。

      在解释我的解决方案之前,先了解一下变体的背景。

      如果我写:

      Dim I as Long
      

      I 将始终是一个长整数。

      在运行时,编译器/解释器在遇到I时不必考虑是什么:

       I = I + 5
      

      但假设我写:

      Dim V as Variant
      
      V = 5
      V = V + 5
      V = "Test"
      V = V & " 1"
      

      这是完全有效(有效但不合理)的代码,因为 Variant 可以包含数字、字符串或工作表。但是每次我的代码访问 V 时,解释器都必须检查 V 的当前内容的类型,并决定它是否适合当前情况。这很耗时。

      我不想阻止您在适当的时候使用变体,因为它们可能非常有用,但您需要了解它们的开销。

      接下来我希望提倡使用有意义和系统的名称。我根据我使用多年的系统命名我的变量。我可以查看我的任何程序/宏并知道变量是什么。当我需要更新我在 12 或 15 个月前编写的程序/宏时,这是一个实时节省程序。

      我不喜欢:

      Dim Assets As Variant
      Assets = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
      

      因为“pipe_mat_tables”等不是资产;它们是工作表的名称。我会写:

      Dim WshtNames As Variant
      WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
      

      我的第一个提议是:

      Option Explicit
      Sub Test1()
      
        Dim WshtNames As Variant
        Dim WshtNameCrnt As Variant
      
        WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
      
        For Each WshtNameCrnt In WshtNames
          With Worksheets(WshtNameCrnt)
            Debug.Print "Cell B1 of worksheet " & .Name & " contains " & .Range("B1").Value
          End With
        Next WshtNameCrnt
      
      End Sub
      

      我本可以将 WshtNameCrnt 命名为 WshtName,但我被告知名称应至少相差三个字符,以避免使用错误的字符而不会引起注意。

      Array 函数返回一个包含数组的变量。 For Each 语句的控制变量必须是对象或变量。这就是我将WshtNamesWshtNameCrnt 定义为变体的原因。请注意,您的解决方案有效,因为工作表是一个对象。

      我使用了With Worksheets(WshtNameCrnt),这意味着匹配End With 之前的任何代码都可以通过在开头添加句点来访问此工作表的组件。所以.Name.Range("B1").Value 引用Worksheets(WshtNameCrnt) 而不选择工作表。这比任何替代方法都更快、更清晰。

      我使用了Debug.Print 而不是MsgBox,因为它不那么麻烦。我的代码无需为每个工作表按 Return 即可运行,并且我在即时窗口中有一个整洁的列表,我可以在闲暇时检查它。在开发过程中,我的代码中经常有许多 Debug.Print 语句,这就是为什么我输出一个句子而不仅仅是工作表名称或单元格值的原因。

      我的第二个奉献是:

      Sub Test2()
      
        Dim InxW As Long
        Dim WshtNames As Variant
      
        WshtNames = Array("pipe_mat_tables", "pipe_diam_tables", "pipe_length_tables")
      
        For InxW = LBound(WshtNames) To UBound(WshtNames)
           With Worksheets(WshtNames(InxW))
            Debug.Print "Cell B1 of worksheet " & .Name & " contains " & .Range("B1").Value
          End With
        Next InxW
      
      End Sub
      

      此宏与第一个宏具有相同的效果。我有时会发现ForFor Each 更方便,尽管在这种情况下我看不出任何一种优势。请注意,我已经写了LBound(WshtNames),即使 WshtNames 的下限总是为零。这只是我(过度?过度?)精确。

      希望这会有所帮助。

      【讨论】:

      • 感谢您的意见。由于我的任务是创建具有很少编程经验的 VBA 程序,因此我很容易走捷径。
      • @squarah 在全球范围内,经理们似乎都希望员工开发计划只是在互联网上提问。你似乎已经获得了一些经验,因为你表现出比许多人更好的困惑。正如您所发现的,这里有些人很乐意提供帮助。欢迎享受编程的乐趣。
      • @TonyDallimore 感谢您的精彩解释。你应该写一本书。
      • @Mertinc 感谢您的积极反馈。我正在尝试在 SO 文档页面中创建对 Outlook VBA 的介绍。我的困难是找时间..
      • 很晚了,但只是想感谢@TonyDallimore,因为指出我不应该使用 ActiveX 并展示如何使用“With X”和“。”语法极大地帮助了我构建我正在从事的这个项目。
      猜你喜欢
      • 2017-11-26
      • 2014-11-15
      • 2022-11-10
      • 1970-01-01
      • 2021-09-04
      • 2023-03-23
      • 1970-01-01
      • 2018-08-23
      • 1970-01-01
      相关资源
      最近更新 更多