VBA 枚举数据库






4.83/5 (8投票s)
本文旨在通过创建枚举数据库,为所有用户定义的枚举添加更多细节。
引言
枚举是开发人员的重要资源,并包含在许多开发语言中。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 项目,但在本项目的案例中,这些情况被放大。
- VBA 不支持评估包含在字符串变量中的命名常量
- 当 Office 项目被保护或锁定以供查看时,其源代码不可访问
- 私有枚举在其作用域之外不可访问
条件一和条件二都提供了变通功能。条件一直接关系到解析器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
请注意,CreateBase
和 DestroyBase
过程都不是数据库的一部分。它们被包含进来只是为了强调数据库可以是一个公开可用的对象这一概念。数据库对象何时何地可用,最佳实践将由开发人员决定。
数据开发
目前,在开发数据库时使用两个过程。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 项目中的用户定义枚举组成。
通常,乐观解析器可能会有些不灵活。数据需要采用预定义的格式,这意味着增加了维护工作。但是,乐观也可能意味着效率,因为解析数据集所需的假设减少了。
速度是关键,这个源代码解析器需要将一部分工作负载卸载给开发人员。开发人员的工作负载以一致的枚举格式的形式出现,并且在大多数情况下,不需要对枚举进行重新格式化。
因此,这个乐观解析器只需几个简单的要素即可成功。遵循一些基本的规则,保持枚举格式的一致性,然后从努力中受益,获得一些高级功能。
解析器规则
所有用户定义的枚举块必须遵守以下三条规则...
- 声明、成员和终止块必须分开,并单独成行。
- 转义名称和常量不能包含等号 (=) 或撇号 (') 字符
- 枚举块内不能使用不可打印字符(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 应用程序中,请按照以下步骤操作...
- 验证目标 Office 应用程序是否支持 VBE 接口
- 将您的 Office 项目文件移动到其自己的文件夹中
- 将 CEnumBase 和 MEnumBase 都导入到 Office 项目中
- 编译并保存项目。
请注意,将 Office 项目文件移动到其自己的文件夹应视为强制性。详细信息请参阅数据文件部分。
版本历史
版本 | 发布日期 |
1.0 | 2014.01.02 |
- 发布说明 - 版本 1.0
- 初始发布
分发
完整的数据库包含在 EnumBase.zip 分发中。无需其他文件即可安装和使用数据库。请注意,虽然提供了离线文档,但本文将始终包含最新信息和分发。
EnumBase.zip | *必需 | ||
CEnumBase | 数据库类 | ||
MEnumBase | 八个支持枚举 | ||
Examples.zip | |||
MCopybook | 离线文档 | ||
MTest | 测试环境和示例 |
Office VBE 支持
为了在此项目中使用代码库,任何目标 Office 应用程序都必须支持 VBE 接口。确定 Office 应用程序是否支持该接口的简单方法是使用以下步骤...
- 创建要测试的任何 Office 应用程序的新实例
- 打开新实例的VBA IDE窗口
- 导航到即时窗口并键入 Application.VBE
IntelliSense 弹出窗口应该会出现,如果 VBE 包含在列表中,那么这个项目很有可能在该应用程序和版本中工作。如果 VBE 没有出现在列表中,那么这个项目将无法在测试的应用程序版本中工作。
故障排除提示
如果遇到问题,有两种途径可以返回类生成的消息。在开发环境中,启用DebugOn开关。消息将定向到即时窗口。
消息也可以通过数据库 Last 属性获得。虽然 Last 属性主要是作为基于代码的消息解决方案设计的,但它始终可用。请注意,该属性会被后续调用数据库过程所清除和重用。
其他一些可能证明有用的提示...
- 确保项目编译无错误
- 验证对项目文件夹的读写访问是否可用
如果需要,请确认“信任对 VBA 项目对象模型的访问”已启用。在 Office 2003 中,它显示为宏安全设置。在 Office 2007 及更高版本中,它是信任中心设置的一部分。有关更多信息,请参阅Microsoft 知识库文章 KB282830。
项目引用
我经常想知道没有微软的代码,在这种情况下,没有 VBA 团队,开发世界会是什么样子。因此,功劳归功于应得之人,除了一个链接,本文中所有链接和参考文献都属于微软,并附上我的感谢。
- VBA 代码模块的解构
- 自动化 Visual Basic 编辑器
- 声明变量
- 即时窗口
- 锁定解决方案的 VBA 项目
- 命名文件、路径和命名空间
- 进程、线程和公寓
- Office 客户端开发
- 对 Office VBA 项目的编程访问被拒绝
- Enum 语句
- Visual Basic 中的类型字符
- Office 2013 VBA 语言参考
- Visual Basic 加载项模型
- Windows 1252 字符集
最终想法
解析枚举可以成为任何对象模型中的一个重要进步。其中一个优点是可以减少重复。然而,它也为跟踪消息、预连接日志记录等等打开了大门。
最后,本文中的文档和代码库都相当冗长。如果您偶然发现任何需要注意的地方,请务必提出建设性意见,因为我随时待命并会回答所有问题。