65.9K
CodeProject 正在变化。 阅读更多。
Home

VBA 枚举数据库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (8投票s)

2014年1月2日

CPOL

18分钟阅读

viewsIcon

47111

downloadIcon

1540

本文旨在通过创建枚举数据库,为所有用户定义的枚举添加更多细节。

引言

枚举是开发人员的重要资源,并包含在许多开发语言中。VBA 语言支持枚举,但只提供对成员值的访问。

本文旨在通过创建枚举数据库,为所有用户定义的枚举添加更多细节。

概述

本项目在活动的 VBA 项目中创建了一个用户定义枚举的数据库。可以提取单个枚举、整个组件或所有用户定义枚举并将其添加到数据库中。

枚举数据库通过对项目源代码进行实时解析来开发。一旦开发完成,数据库可以保存和从数据文件加载,或者实时解析可以持续进行,直到项目被保护

一个已开发的数据库能够通过成员值返回成员名称和注释。成员列表可以格式化用于下拉列表,数据库可以返回枚举的最小值-最大值

枚举数据以非二进制、ANSI 格式存储,易于重用。此外,还包含了一个通用数据查看器,允许在文本编辑器中查看数据库元素。

要求

除了访问项目中的源代码和一些文件 I/O,枚举数据库是纯粹的 VBA。唯一真正的要求是任何目标 Office 应用程序必须支持 VBE 接口

官方地,枚举数据库支持 MS Office 2003 或更高版本,因为那是我能测试的最早版本。如果您发现它可用于其他版本的Office,请告诉我,我将考虑更新要求。

虽然不是必需的,但下载我文章《VBA 代码模块的解构》中的 MProject 模块可能会很有用。该模块可以备份您的工作,并为代码库提供一些清晰度。

通过示例

考虑到本文档的篇幅,归根结底,这个数据库非常易于使用。秉承这一前提,一个简短的数据库对话示例有时胜过千言万语。

请注意,这仅仅是一个示例。在实践中,当功能分离并以更广泛的范围进行设计时,对话将达到最佳效果。

另请注意,对话可能会启动两个系统分配的文本编辑器实例。一个实例用于查看解析器查找报告,另一个实例用于查看当前加载的数据

Public Sub Conversation()

    'Create a database instance
    Dim EBase As CEnumBase
    Set EBase = New CEnumBase

    'Enable messaging and parser reporting
    EBase.DebugOn = True
    EBase.ReportOn = True

    'Add every enumeration in the active project
    EBase.AddAll
    Debug.Print "EnumCount : " & CStr(EBase.ECount)

    'Save the database to a file
    EBase.ToFile
    Debug.Print "Datafile : " & EBase.Last(LastType_Path)

    'Shell the loaded data to the system assigned text editor
    Call EBase.Data(True, True)

    'Define an enumeration
    Dim TheEnum As String
    TheEnum = "MEnumBase.MinMaxEnum"

    'MemberName by value
    Debug.Print "MemberName : " & EBase.Parse(TheEnum, MinMax_Both)

    'MemberList
    Debug.Print "MemberList : " & EBase.Members(TheEnum)

    'Cleanup
    Set EBase = Nothing

End Sub

流程

Public

    Add   添加单个组件或目标枚举
    添加所有   添加活动项目中的所有枚举
    CCount   已加载组件的数量
    Components   已加载组件列表
    Data   枚举数据库
    DataLen   枚举数据库的长度
    DebugOn   启用或禁用 Debug.Print 消息
    ECount   已加载枚举的数量
    枚举   已加载枚举列表
    FromFile   从文件加载数据库
    Last   上次错误、消息和路径
    LookupOn   启用或禁用解析器查找功能
    MCount   枚举成员计数
    成员   枚举成员列表
    MinMax   枚举的最小值和最大值
    解析   使用成员值返回成员名称或注释
    项目   当前项目属性
    移除   从数据库中移除枚举或组件
    ReportOn   启用或禁用解析器报告
    ResetData   完全重置数据库,将所有值设置为零
    ToFile   将数据库保存到文件
    版本   当前数据库版本
    视图   通用数据查看器

Private

    添加对象   Add 和 AddAll 过程的基本功能
    CreatePath       如果文件夹路径不存在,则创建它
    提取       源代码解析器
    查找       查找常量格式的成员值
    MBlock       成员块查找
    Message       通用消息陷阱
    ReadWrite       通用文件读写过程
    ResetMsg       重置上次错误、消息和路径
    SortLong       一维长整型数组的快速排序
    SortText       一维字符串数组的快速排序
    SortTwo       二维 Variant 数组的快速排序
    SplitPath       VBA 对象路径分隔符

变通方法

在使用此代码库和一般 VBA 时,存在三种需要适当可见性的情况。这些情况适用于所有 VBA 项目,但在本项目的案例中,这些情况被放大。

  1. VBA 不支持评估包含在字符串变量中的命名常量
  2. 当 Office 项目被保护或锁定以供查看时,其源代码不可访问
  3. 私有枚举在其作用域之外不可访问

条件一和条件二都提供了变通功能。条件一直接关系到解析器Lookup过程的创建,条件二直接关系到从数据文件保存和检索枚举数据库

条件三是VBA 对象模型的设计使然,没有可开发的解决方案。尽管该条件是设计使然,但它仍然很重要。请阅读私有枚举部分以获取更多信息。

创建实例

枚举数据库由两个组件组成,两者都是必需的。CEnumBase 是数据库和一个 VBA 类对象。MEnumBase 是一个标准模块,其中包含八个支持数据库的枚举。

与其他 VBA 类对象一样,必须先创建 CEnumBase 类的实例才能使用它。同理,使用完毕后应将其释放。

在以下示例中,CEnumBase 类被声明为公共对象。然后使用两个过程,一个用于创建实例,一个用于释放实例。

'Accessible Object
Public EBase As CEnumBase

'Create a database instance
Public Sub CreateBase()
    Set EBase = New CEnumBase
End Sub

'Destroy the database instance
Public Sub DestroyBase()
    Set EBase = Nothing
End Sub

请注意,CreateBaseDestroyBase 过程都不是数据库的一部分。它们被包含进来只是为了强调数据库可以是一个公开可用的对象这一概念。数据库对象何时何地可用,最佳实践将由开发人员决定。

数据开发

通过将用户定义的枚举添加到数据库实例中来开发枚举数据库

目前,在开发数据库时使用两个过程。Add 过程用于添加单个组件和目标枚举。AddAll 过程只是添加活动项目中的所有用户定义枚举。

许多枚举可以按原样解析并包含在内,但可能会有例外。要理解这些例外,首先要了解解析器如何派生成员值

派生成员值

源代码解析器用于解析代码模块中的枚举数据。在解析过程中,所有枚举成员值都会被确定。解析器将首先尝试通过源代码强制转换来派生成员值。

在此代码库中,源代码强制转换仅仅是将源代码文本转换为数值的能力。然而,在某些情况下,成员值可能以命名常量或表达式的形式出现,解析器无法转换。

Public Enum NameOne
    One = 1              'Will derive by coercion
    Two = 2              'Will derive by coercion
    [_First] = One       'Will only derive by lookup
    Three = (One + Two)  'Will only derive by lookup
End Enum

解析器无法转换成员值时,它会将当前的MemberPath发送给Lookup过程。Lookup过程是一种将MemberPath字符串转换为实际值的简单方法。

'Lookup
Select Case MemberPath
    Case "MEnums.NameOne.[_First]"
        Lookup = CStr(MEnums.NameOne.[_First])
    Case "MEnums.NameOne.Three"
        Lookup = CStr(MEnums.NameOne.Three)
End Select

Lookup 过程使用一个 Select-Case 语句,该语句检查匹配的 MemberPath。如果找到匹配项,则将关联的值返回给解析器。

关键概念是,当无法通过源代码强制转换派生值,并且通过调用 Lookup 过程未返回值时,该成员将从数据库实例中省略。同理,一个枚举必须至少有一个有效成员才能包含在数据库中。

私有枚举

在开发软件时,保持对象和数据私有是一项重要的资源。然而,在此代码库中,可能会出现一种无法开发的变通情况。

潜在的情况是存在一个带有常量格式成员值的私有枚举。源代码强制转换无法用于解析该值,并且查找映射将失败,因为枚举成员由于其私有作用域而不可访问。

Private Enum NameOne
    One = 1              'Will derive by coercion
    Two = 2              'Will derive by coercion
    [_First] = One       'Lookup will fail member is private
    Three = (One + Two)  'Lookup will fail member is private
End Enum

此情况是VBA 对象模型的设计使然。在这种情况下,功能更改应转向应用程序设计层面,因为任何更改都需要在该层面进行。

对象路径

目前,数据库中的过程使用三种对象路径变体。无论何时请求其中一个路径,都需要对象路径的字符串表示,而不是对象实例。

这些路径旨在允许某些过程重用功能,并采用统一的方法使用它们。

    组件路径       ComponentName 或 ComponentName.EnumerationName
    枚举路径       EnumerationName 或 ComponentName.EnumerationName
    成员路径       ComponentName.EnumerationName.MemberName

当要求提供完整路径以从数据文件保存或加载数据库时,请使用以下两种格式之一...

    标准版                   驱动器:\文件夹\文件名.扩展名
    UNC 路径                   \\服务器\文件夹\文件名.扩展名

虽然示例中使用了反斜杠字符,但实际的文件夹分隔符将取决于系统。如果需要,可以使用数据库Project属性来返回当前系统的文件夹分隔符。

乐观解析器

枚举数据库采用乐观方法提取数据。也就是说,解析器乐观地认为数据将采用预定义格式。在这种情况下,数据由 VBA 项目中的用户定义枚举组成。

通常,乐观解析器可能会有些不灵活。数据需要采用预定义的格式,这意味着增加了维护工作。但是,乐观也可能意味着效率,因为解析数据集所需的假设减少了。

速度是关键,这个源代码解析器需要将一部分工作负载卸载给开发人员。开发人员的工作负载以一致的枚举格式的形式出现,并且在大多数情况下,不需要对枚举进行重新格式化。

因此,这个乐观解析器只需几个简单的要素即可成功。遵循一些基本的规则,保持枚举格式的一致性,然后从努力中受益,获得一些高级功能

解析器规则

所有用户定义的枚举块必须遵守以下三条规则...

  1. 声明、成员和终止块必须分开,并单独成行。
  2. 转义名称和常量不能包含等号 (=) 或撇号 (') 字符
  3. 枚举块内不能使用不可打印字符(2,3,4,5,6,19,20)

如果这些规则没有得到维护,解析器可能会返回一个损坏的数据库。另请注意,此解析器不知道如何拆分内联枚举或如何处理行继续符。

所需格式

NameOne 枚举的格式是 解析器 所需的格式。声明、终止块和每个成员都独立成行。隐藏成员、转义名称和注释都是有效的。

Public Enum NameOne       'Declaration
   One = 1                'Standard member
   [Two] = 2              'Escaped member
   Three                  'Compiler assigned member
   [_First] = One         'Hidden member
   [_Last] = [Three]      'Hidden member
End Enum                  'Terminating block

请注意,示例中声明了 Public 作用域。虽然 解析器 不要求枚举显式声明作用域,但建议这样做。

另请注意,数据库中仅保留成员行内注释。所有其他注释和空白行在数据开发期间都被剥离。

以下是反面例子,它要么无法开发,要么返回损坏的数据库,要么导致枚举成员被省略

Public Enum [Name'One]    'Escaped apostrophe will fail
   [One=Hi] = 1           'Escaped equal sign will fail
   [Two'There] = 2        'Escaped apostrophe will fail
   Three = [Two'There]    'Escaped apostrophe will fail
End Enum

数据库

枚举数据库由四个主要变量组成,尽管大部分实际枚举数据存储在一个非二进制、ANSI 格式的字符串变量中。文本由分隔符分隔,分隔符可用于发现记录边界。

单个枚举记录由父组件名称、枚举名称以及至少一个成员名称及其值组成。

    

当请求保留注释并且存在注释时,成员块还将包含一个字符 19 分隔符,后跟注释。请注意,数据库中存储的所有值都是十六进制格式,不带标准的十六进制前缀 (&H)

分隔符

以下分隔符用于创建完整的枚举记录。这些分隔符不能用于任何要添加到数据库实例的枚举块中。

    字符    符号    描述
    02 )组件名称开始
    03 )枚举名称开始
    05 )十六进制值开始
    06 )成员名称开始
    19 )注释开始
    05 )成员块分隔符
    04 )枚举结束

以下标准 VBA 分隔符用于初始源代码解析。源代码解析后,这些分隔符将映射到数据库实例中的字符 19 和字符 06 分隔符。

    字符    符号    描述            ToChr    符号    
    39 )标准 VBA 注释        19 )
    61( = )标准 VBA 值        06 )

字符 20 分隔符用于分隔枚举数据文件中的每个字段,不能用于任何要添加到数据库实例的枚举块中。

    字符    符号    描述
    20 )字段分隔符

符号

    a.   枚举按照添加的顺序物理排列
    b.   枚举首尾相接
    c.   每个数据库实例只能添加一次枚举
    d.   零成员的枚举被省略
    e.   未解析值的成员将被单独省略
    f.   当组件在数据库中不存在时,可以添加完整的组件。
    g.   ComponentName 为每个枚举重复一次
    h.   HexValue 是一个不带 (&H) 前缀的十六进制值
    i.   HexValue-MemberName-Comment 段对每个成员重复
    j.   只有当成员注释存在并被请求时才包含注释段
    k.   只能保留成员行内注释
    l.   编译器分配的成员值可用于强制查找
    m.   搜索和替换不区分大小写
    n.   字符排序不区分大小写
    o.   数据库中的第一个字符是字符 02
    p.   数据库中的最后一个字符是字符 04

开关

目前,枚举数据库使用三个开关来控制消息和解析器查找功能。这些开关如下...

    DebugOn
    启用或禁用即时窗口消息
    默认值 = False
     
    此开关允许在即时窗口中显示数据库消息和错误。无论是否启用,消息始终可通过数据库实例的Last属性获取。
     
    LookupOn
    解析源代码时执行查找
    默认 = True
     
    启用时,解析器将根据需要使用 Lookup 过程来解析成员值。禁用时,不使用 Lookup 过程。请注意,除非同时启用 LookupOn 和 ReportOn 开关,否则不会生成解析器报告。
     
    ReportOn
    解析源代码时生成未解析成员值的报告
    默认值 = False
     
    当开关启用时,并且仅在解析期间,具有无法解析值的枚举成员将被收集在报告中。报告包含创建查找映射所需的精确MemberPath,并可在即时窗口中显示或发送到数据查看器

数据文件

每当 Office 项目被保护时,实时源代码解析就不可用。因此,枚举数据库可以根据需要从数据文件中保存和加载。

默认数据文件位置是 Office 项目文件当前位置的子文件夹。因此,将 Office 项目移动到其自己的文件夹应视为强制性。默认值如下...

    Location   在 Office 项目文件位置创建的文件夹
    文件夹名称   EBase
    数据文件名   OfficeProjectFilename_ebase.dat

虽然提供了默认值以保持一致性和易用性,但可以指定任何有效的文件夹和文件名。另请注意,在需要时,类将在保存数据文件时自动创建目标路径

缓冲区配置文件

在分析此解析器及其缓冲区时,注意到从未达到缓冲区阈值,并且不需要额外的空间分配。事实上,可以潜在地采用负填充。

缓冲区的基本模型是模块声明部分的大小,减去找到的第一个枚举的位置。在大多数情况下,基本模型明显高估了初始分配需求。

与其在数据库中添加一个填充开关并向下建模缓冲区,不如首先从初始分配速度和整体性能的角度检查缓冲区。决定因素如下...

  • 使用 Space$ 函数的标准分配调用相对较快。分配多达 300kB 的平均时间不到 1 毫秒。

  • 使用 2310 行枚举,解析最终开发大小为 50KB 的数据加载的平均速度为 17 毫秒。

  • 最大的单一瓶颈是调用VBE CodeModule对象,并为请求的数据进行本地分配。平均而言,该调用需要 2 毫秒。

最终结果是,虽然初始缓冲区分配被夸大了,但简洁的缓冲区带来的任何性能提升都将微不足道。该结论的权重受到单线程单元的影响,该单元将受到其他与正在进行的数据加载不直接相关的性能下降的影响。

有趣的是,最快的整体加载时间总是通过使用数据文件实现的。一个 50KB、预解析和预开发的数据文件平均加载时间为 8 毫秒。从时间一致性的角度来看,按需加载数据库很有吸引力,尽管它依赖于文件系统。

参数词汇表

    所有组件   所有用户定义的 VBA 组件
    CommentDelimiter   用于分隔名称和注释的分隔符
    ComponentName   用户定义的 VBA 组件的名称
    组件路径   ComponentName 或 ComponentName.EnumerationName
    枚举路径   EnumerationName 或 ComponentName.EnumerationName
    ExitOnFirst   找到第一个值后退出
    FormatValue   以长整型或十六进制格式返回数值
    FullPath   包含驱动器、文件夹、文件名和扩展名的路径
    IncludeComments   添加枚举时包含成员行内注释
    IncludeHiddenMembers   添加枚举时包含隐藏成员
    LastType   类消息类型(错误、消息、路径)
    ListSeparator   分隔列表项的字符
    MakePath   如果路径不存在,则创建它
    成员路径   ComponentName.EnumerationName.MemberName
    PropertyValue   项目属性值类型
    RemoveCharacters   要从成员名称中删除的字符
    ResetMessages   重置上一个属性容器
    ReturnColumns   定义要返回的成员列
    ReturnField   返回成员名称、注释或两者
    ReturnQualified   返回组件限定路径
    返回值   单个值或多个值
    Sort   返回排序后的记录列表
    SortColumn   用于排序列表的列
    SplitRecords   拆分数据库记录
    ToViewer   将数据发送到内置数据查看器
    ValueDelimiter   分隔名称和值的字符

安装

枚举数据库CEnumBase类和MEnumBase标准模块组成。

CEnumBase 是数据库,而 MEnumBase 包含八个支持数据库的枚举。这两个组件都是必需的,因为没有其支持枚举,数据库将无法运行。

要将枚举数据库包含在您的 Office 应用程序中,请按照以下步骤操作...

  1. 验证目标 Office 应用程序是否支持 VBE 接口
  2. 将您的 Office 项目文件移动到其自己的文件夹中
  3. CEnumBaseMEnumBase 都导入到 Office 项目中
  4. 编译并保存项目。

请注意,将 Office 项目文件移动到其自己的文件夹应视为强制性。详细信息请参阅数据文件部分。

版本历史

  版本      发布日期    
  1.0      2014.01.02    

  • 发布说明 - 版本 1.0
    • 初始发布

分发

完整的数据库包含在 EnumBase.zip 分发中。无需其他文件即可安装和使用数据库。请注意,虽然提供了离线文档,但本文将始终包含最新信息和分发。

    EnumBase.zip   *必需
    CEnumBase   数据库类
    MEnumBase   八个支持枚举
 
    Examples.zip
    MCopybook   离线文档
    MTest   测试环境和示例

Office VBE 支持

为了在此项目中使用代码库,任何目标 Office 应用程序都必须支持 VBE 接口。确定 Office 应用程序是否支持该接口的简单方法是使用以下步骤...

  1. 创建要测试的任何 Office 应用程序的新实例
  2. 打开新实例的VBA IDE窗口
  3. 导航到即时窗口并键入 Application.VBE

IntelliSense 弹出窗口应该会出现,如果 VBE 包含在列表中,那么这个项目很有可能在该应用程序和版本中工作。如果 VBE 没有出现在列表中,那么这个项目将无法在测试的应用程序版本中工作。

故障排除提示

如果遇到问题,有两种途径可以返回类生成的消息。在开发环境中,启用DebugOn开关。消息将定向到即时窗口

消息也可以通过数据库 Last 属性获得。虽然 Last 属性主要是作为基于代码的消息解决方案设计的,但它始终可用。请注意,该属性会被后续调用数据库过程所清除和重用。

其他一些可能证明有用的提示...

  • 确保项目编译无错误
  • 验证对项目文件夹的读写访问是否可用

如果需要,请确认“信任对 VBA 项目对象模型的访问”已启用。在 Office 2003 中,它显示为宏安全设置。在 Office 2007 及更高版本中,它是信任中心设置的一部分。有关更多信息,请参阅Microsoft 知识库文章 KB282830

项目引用

我经常想知道没有微软的代码,在这种情况下,没有 VBA 团队,开发世界会是什么样子。因此,功劳归功于应得之人,除了一个链接,本文中所有链接和参考文献都属于微软,并附上我的感谢。

最终想法

解析枚举可以成为任何对象模型中的一个重要进步。其中一个优点是可以减少重复。然而,它也为跟踪消息、预连接日志记录等等打开了大门。

最后,本文中的文档和代码库都相当冗长。如果您偶然发现任何需要注意的地方,请务必提出建设性意见,因为我随时待命并会回答所有问题。

© . All rights reserved.