【问题标题】:Hidden features of VBAVBA的隐藏特性
【发布时间】:2010-11-07 10:30:09
【问题描述】:

VBA 语言的哪些功能文档记录不充分,或者根本不经常使用?

【问题讨论】:

  • @bbqchickenrobot 我没有太多选择,真的。无论如何,在小剂量情况下,它并没有那么糟糕。

标签: vba hidden-features


【解决方案1】:

此技巧仅适用于 Access VBA,Excel 和其他不允许。但是您可以通过在模块名称前加上下划线来使标准模块对对象浏览器隐藏。只有当您将对象浏览器更改为显示隐藏对象时,该模块才会可见。

此技巧适用于所有基于 vb6 的 VBA 版本中的枚举。您可以通过将其名称括在括号中来创建 Enum 的隐藏成员,然后在其前面加上下划线。示例:

Public Enum MyEnum
    meDefault = 0
    meThing1 = 1
    meThing2 = 2
    meThing3 = 3
    [_Min] = meDefault 
    [_Max] = meThing3 
End Enum

Public Function IsValidOption(ByVal myOption As MyEnum) As Boolean
    If myOption >= MyEnum.[_Min] Then IsValidOption myOption <= MyEnum.[_Max]
End Function

在 Excel-VBA 中,您可以通过将单元格括在方括号中来引用单元格,方括号还可以用作 evaluate command,允许您评估公式语法:

Public Sub Example()
    [A1] = "Foo"
    MsgBox [VLOOKUP(A1,A1,1,0)]
End Sub

您还可以通过将 LSet 与相同大小的用户定义类型相结合,在不使用 MemCopy (RtlMoveMemory) 的情况下传递原始数据:

Public Sub Example()
    Dim b() As Byte
    b = LongToByteArray(8675309)
    MsgBox b(1)
End Sub

Private Function LongToByteArray(ByVal value As Long) As Byte()
    Dim tl As TypedLong
    Dim bl As ByteLong
    tl.value = value
    LSet bl = tl
    LongToByteArray = bl.value
End Function

八进制和十六进制文字实际上是无符号类型,它们都将输出 -32768:

Public Sub Example()
    Debug.Print &H8000
    Debug.Print &O100000
End Sub

如前所述,在括号内传递变量会导致它被 ByVal 传递:

Sub PredictTheOutput()
    Dim i&, j&, k&
    i = 10: j = i: k = i
    MySub (i)
    MySub j
    MySub k + 20
    MsgBox Join(Array(i, j, k), vbNewLine), vbQuestion, "Did You Get It Right?"
End Sub

Public Sub MySub(ByRef foo As Long)
    foo = 5
End Sub

您可以将字符串直接分配到字节数组中,反之亦然:

Public Sub Example()
    Dim myString As String
    Dim myBytArr() As Byte
    myBytArr = "I am a string."
    myString = myBytArr
    MsgBox myString
End Sub

“Mid”也是一个运算符。使用它,您可以覆盖字符串的特定部分,而无需 VBA 众所周知的慢字符串连接:

Public Sub Example1()
    ''// This takes about 47% of time Example2 does:
    Dim myString As String
    myString = "I liek pie."
    Mid(myString, 5, 2) = "ke"
    Mid(myString, 11, 1) = "!"
    MsgBox myString
End Sub

Public Sub Example2()
    Dim myString As String
    myString = "I liek pie."
    myString = "I li" & "ke" & " pie" & "!"
    MsgBox myString
End Sub

【讨论】:

    【解决方案2】:

    Mid() 语句有一个重要但几乎总是遗漏的特性。这就是 Mid() 出现在赋值的左侧,而不是 Mid() 函数出现在右侧或表达式中的位置。

    规则是如果目标字符串不是字符串文字,并且这是对目标字符串的唯一引用,并且插入的段的长度与被替换的段的长度匹配,那么字符串将被视为可变的操作。

    这是什么意思?这意味着,如果您将大型报告或大量字符串列表构建为单个字符串值,那么利用它将使您的字符串处理速度更快。

    这是一个从中受益的简单类。它为您的 VBA 提供了与 .Net 相同的 StringBuilder 功能。

    ' Class: StringBuilder
    
    Option Explicit
    
    Private Const initialLength As Long = 32
    
    Private totalLength As Long  ' Length of the buffer
    Private curLength As Long    ' Length of the string value within the buffer
    Private buffer As String     ' The buffer
    
    Private Sub Class_Initialize()
      ' We set the buffer up to it's initial size and the string value ""
      totalLength = initialLength
      buffer = Space(totalLength)
      curLength = 0
    End Sub
    
    Public Sub Append(Text As String)
    
      Dim incLen As Long ' The length that the value will be increased by
      Dim newLen As Long ' The length of the value after being appended
      incLen = Len(Text)
      newLen = curLength + incLen
    
      ' Will the new value fit in the remaining free space within the current buffer
      If newLen <= totalLength Then
        ' Buffer has room so just insert the new value
        Mid(buffer, curLength + 1, incLen) = Text
      Else
        ' Buffer does not have enough room so
        ' first calculate the new buffer size by doubling until its big enough
        ' then build the new buffer
        While totalLength < newLen
          totalLength = totalLength + totalLength
        Wend
        buffer = Left(buffer, curLength) & Text & Space(totalLength - newLen)
      End If
      curLength = newLen
    End Sub
    
    Public Property Get Length() As Integer
      Length = curLength
    End Property
    
    Public Property Get Text() As String
      Text = Left(buffer, curLength)
    End Property
    
    Public Sub Clear()
      totalLength = initialLength
      buffer = Space(totalLength)
      curLength = 0
    End Sub
    

    下面是一个如何使用它的例子:

      Dim i As Long
      Dim sb As StringBuilder
      Dim result As String
      Set sb = New StringBuilder
      For i = 1 to 100000
        sb.Append CStr( i)
      Next i
      result = sb.Text
    

    【讨论】:

      【解决方案3】:

      VBA 本身似乎是一个隐藏功能。我认识的多年来一直使用 Office 产品的人都不知道它甚至是套件的一部分。

      我已经在这里发布了多个问题,但对象浏览器是我的秘密武器。如果我需要 ninja 快速编写代码,但不熟悉 dll,对象浏览器可以挽救我的生命。它比 MSDN 更容易学习类结构。

      Locals 窗口也非常适合调试。在您的代码中暂停一下,它将显示当前命名空间中的所有变量、它们的名称以及它们的当前值和类型。

      谁能忘记我们的好朋友即时窗口?它不仅对 Debug.Print 标准输出非常有用,而且您也可以在其中输入命令。想知道什么是 VariableX?

      ?VariableX
      

      需要知道那个单元格是什么颜色?

      ?Application.ActiveCell.Interior.Color
      

      事实上,所有这些窗口都是使用 VBA 提高工作效率的好工具。

      【讨论】:

      • “我知道多年来使用 Office 产品的人都不知道它甚至是套件的一部分。” - 这就是让我的部分业务保持活力的原因,我被要求做这种工作的事实。如果他们学会了如何去做(和/或擅长它)——我就会失业。 :-)
      • 我也是。我觉得 VBA 会变成昔日的 COBOL。 VBA 应用程序仍然存在并且需要更新,但人们将转向更性感的语言。
      • MSDN 上的类结构?首先,您必须转到“MyClass”以获取无用的概述和很少使用的示例,然后转到“MyClass Methods”以获取方法列表,然后转到“MyClass Properties”以查找属性......然后是层次结构......你知道MSDN 通常是我最后的选择。
      • "但是对象浏览器是我的秘密武器" 它也是.NET的秘密武器。
      • @mandroid:当然。对于愿意帮助移植旧版 VBA 应用程序的熟练 .NET 开发人员来说,有很多工作要做。许多中小型企业完全依赖于使用 VBA 在 Office 之上构建的业务系统。
      【解决方案4】:

      这不是一个特性,而是我在 VBA(和 VB6)中多次看到错误的事情:在方法调用上添加括号,它将改变语义:

      Sub Foo()
      
          Dim str As String
      
          str = "Hello"
      
          Bar (str)
          Debug.Print str 'prints "Hello" because str is evaluated and a copy is passed
      
          Bar str 'or Call Bar(str)
          Debug.Print str 'prints "Hello World"
      
      End Sub
      
      Sub Bar(ByRef param As String)
      
          param = param + " World"
      
      End Sub
      

      【讨论】:

      • +1 我还没有找到一个实际需要 Call 关键字的示例。 Call foo(bar) 总是可以替换为 foo bar 所以 Call 似乎是多余的。通过关于 SO 的 VBA 问题,许多人似乎不知道括号不需要调用 Sub,因此我们可以将其称为难以理解的功能
      • 如果 Bar 是一个函数并且您使用 Bar(str) 而不是 Bar (str) 括号不要覆盖 ByRef 关键字。
      • @Kuyenda:编译代码时VBA-IDE会自动插入空格。你是如何运行Bar(str) 版本的?
      • 这是语言的一个特点。添加括号允许您传递参数 ByVal 而不是 ByRef。有关详细信息,请参阅msdn.microsoft.com/en-us/library/chy4288y.aspx 上的“如何:强制按值传递参数”。
      • 这里 VB6 参考,“将变量转换为表达式的最简单方法是将其括在括号中。”在msdn.microsoft.com/en-us/library/aa263527(VS.60).aspx
      【解决方案5】:

      隐藏的功能

      1. 虽然它是“Basic”,但您可以使用 OOP - 类和对象
      2. 您可以进行 API 调用

      【讨论】:

      • 对于那些说 VBA 中的 OO 不是 OO 的人,请克服它。接口继承也是继承——只是不同的一种。
      • 我喜欢 VBA 中对 OO 的(尽管有限)支持。尤其是实现多个接口的能力。
      • 这两个功能几乎没有隐藏吧?
      • 隐藏这个词是主观的。我认识很多 VBA 用户(不一定是开发人员),他们不知道这两个功能,但仍然大量使用该语言。
      【解决方案6】:

      VBA 中记录最少的功能可能是那些您只能通过在 VBA 对象浏览器上选择“显示隐藏成员”来公开的功能。隐藏成员是那些在 VBA 中但不受支持的函数。您可以使用它们,但微软可能会随时删除它们。他们都没有提供任何文档,但你可以在网上找到一些。这些隐藏功能中谈论最多的可能是对 VBA 中指针的访问。要获得体面的文章,请查看; Not So Lightweight - Shlwapi.dll

      已记录,但可能更模糊(无论如何在 excel 中)正在使用 ExecuteExcel4Macro 访问属于整个 Excel 应用程序实例而不是特定工作簿的隐藏全局命名空间。

      【讨论】:

      • +1 用于 ExecuteExcel4Macro 技巧,我想知道你是如何从代码中做到这一点的。
      【解决方案7】:

      您可以使用Implements 关键字实现接口。

      【讨论】:

        【解决方案8】:

        字典。没有它们,VBA 几乎一文不值!

        参考 Microsoft Scripting Runtime,将Scripting.Dictionary 用于任何足够复杂的任务,从此过上幸福的生活。

        Scripting Runtime 还为您提供 FileSystemObject,强烈推荐。

        从这里开始,然后挖掘一下......

        http://msdn.microsoft.com/en-us/library/aa164509%28office.10%29.aspx

        【讨论】:

          【解决方案9】:

          输入 VBA. 将显示所有内置函数和常量的智能感知列表。

          【讨论】:

            【解决方案10】:

            通过一些工作,您可以像这样迭代自定义集合:

            ' Write some text in Word first.'
            Sub test()
                Dim c As New clsMyCollection
                    c.AddItems ActiveDocument.Characters(1), _
                        ActiveDocument.Characters(2), _
                        ActiveDocument.Characters(3), _
                        ActiveDocument.Characters(4)
            
                Dim el As Range
                For Each el In c
                    Debug.Print el.Text
                Next
                Set c = Nothing
            End Sub
            

            您的自定义集合代码(在名为 clsMyCollection 的类中):

            Option Explicit
            
            Dim m_myCollection As Collection
            
            Public Property Get NewEnum() As IUnknown
                ' This property allows you to enumerate
                ' this collection with the For...Each syntax
                ' Put the following line in the exported module
                ' file (.cls)!'
                'Attribute NewEnum.VB_UserMemId = -4
                Set NewEnum = m_myCollection.[_NewEnum]
            End Property
            
            Public Sub AddItems(ParamArray items() As Variant)
            
                Dim i As Variant
            
                On Error Resume Next
                For Each i In items
                    m_myCollection.Add i
                Next
                On Error GoTo 0
            End Sub
            
            Private Sub Class_Initialize()
                Set m_myCollection = New Collection
            End Sub
            

            【讨论】:

              【解决方案11】:
              • 输入 debug.? xxx 而不是 debug.print xxx 保存 4 个完整的击键。
              • 在包含任何其他代码的模块顶部添加:enum foo: me=0: end enum 以使其崩溃。

              【讨论】:

                【解决方案12】:

                支持本地化版本,该版本(至少在上个世纪)支持使用本地化值的表达式。就像 Pravda for True 和 Fałszywy(不太确定,但至少它确实有有趣的 L)在波兰语中的 False ......实际上英语版本将能够阅读任何语言的宏,并即时转换。但是,其他本地化版本无法处理。

                失败。

                【讨论】:

                  【解决方案13】:

                  VBE(Visual Basic 可扩展性)对象模型是一个鲜为人知和/或未充分利用的功能。它允许您编写 VBA 代码来操作 VBA 代码、模块和项目。我曾经写过一个 Excel 项目,它可以从一组模块文件中组装其他 Excel 项目。

                  对象模型也适用于 VBScript 和 HTA。我一次写了一个 HTA 来帮助我跟踪大量的 Word、Excel 和 Access 项目。许多项目会使用通用代码模块,模块很容易在一个系统中“增长”,然后需要迁移到其他系统。我的 HTA 允许我导出项目中的所有模块,将它们与公共文件夹中的版本进行比较并合并更新的例程(使用 BeyondCompare),然后重新导入更新的模块。

                  VBE 对象模型在 Word、Excel 和 Access 之间的工作方式略有不同,遗憾的是根本不能在 Outlook 中工作,但仍然提供了很好的代码管理功能。

                  【讨论】:

                    【解决方案14】:

                    IsDate("13.50") 返回TrueIsDate("12.25.2010") 返回False

                    这是因为IsDate 可以更准确地命名为IsDateTime。并且因为句点 (.) 被视为时间分隔符而不是日期分隔符。见here for a full explanation

                    【讨论】:

                      【解决方案15】:

                      VBA 支持位运算符来比较两个值的二进制数字(位)。例如,表达式 4 And 7 计算 4 (0100) 和 7 (0111) 的位值并返回 4(这两个数字中的位)。类似地,表达式 4 Or 8 计算 4 (0100) 中的位值) 和 8 (1000) 并返回 12 (1100),即其中任一为真的位。

                      不幸的是,位运算符在逻辑比较运算符中具有相同的名称:And、Eqv、Imp、Not、Or 和 Xor。这可能会导致模棱两可,甚至产生矛盾的结果。

                      例如,打开即时窗口 (Ctrl+G) 并输入: ? (2和4) 这将返回零,因为 2 (0010) 和 4 (0100) 之间没有共同的位。

                      【讨论】:

                        【解决方案16】:

                        Deftype Statements

                        此功能的存在大概是为了向后兼容。或者编写令人绝望的混淆意大利面条代码。你的选择。

                        【讨论】:

                        • 现在不要对 VBA 太苛刻了。这是直接向后兼容的东西。曾几何时,字节很重要。 (这是我在 60 年代谷歌搜索中可以找到的最早的带有 DEFxxx 语句的 VBA 祖先:en.wikipedia.org/wiki/GW-BASIC
                        • 看起来 IBM BASICA 及其早期变体也支持它。请参阅retroarchive.org/dos/docs/basic_ref_1.pdf 中的第 4-73 页。另请查看第 4-68 页;我希望我当时知道DEF FN。美好的时光……
                        猜你喜欢
                        • 2011-03-19
                        • 1970-01-01
                        • 1970-01-01
                        • 2010-09-15
                        • 2010-11-07
                        • 2010-09-22
                        • 2010-10-31
                        • 2011-01-30
                        • 2010-10-31
                        相关资源
                        最近更新 更多