如何在 VBA 中创建合适的集合?

Tod*_*son 2 collections excel vba

我正在尝试将大型 3 维数组转换为一系列类模块。我将每个下一个类存储为上一个类中的数组。它就像品牌 -> 产品 -> 批次。

我已经成功创建了这个交互并且可以通过名称访问它们,例如:

Sub test()
    Dim MyBrand As Brand
    Set MyBrand = New Brand
    MyBrand.Name = "Company1"
    MyBrand.AddProduct "Shoes"
    MyBrand.Products("Shoes").AddLot "240502"
    MsgBox MyBrand.Products("Shoes").Lots(0) 'Correctly Displays "240502"
End Sub
Run Code Online (Sandbox Code Playgroud)

但后来我想创建一个对象组,可以保存多个Brand对象并像Brands("Company1").

如果我在类模块中使用数组,我最终会得到Brands.Brand("Company1"). 如果我使用Collection,我将不得不使用像Brands(1).

有没有办法创建合适的对象组,以便我可以模仿 Application.Workbooks 等组的语法并按名称引用成员?

Mat*_*don 6

自定义集合背后的许多魔力取决于您无法在 VBE 中编辑的隐藏属性;您需要导出(并在出现提示时从项目中删除)类模块,在 Notepad/Notepad++ 中编辑其魔法成员属性,保存更改,然后将模块重新导入到项目中。

这显然很乏味且容易出错,但有一种(好多)更好的方法。

为了支持这一点:

Set shoesProduct = MyBrand.Products("Shoes")
Run Code Online (Sandbox Code Playgroud)

您可以定义Products为 aDictionary并称其为一天,但是作为概念的封装是......好吧,在这里受到打击(内部集合是 a Dictionary, aCollection还是 .NETArrayList通常应该是其余部分的实现细节的代码不需要关心)。

我怀疑这个Brand类有太多的责任,并且“是”产品集合;最佳做法是将Brand.Products属性定义如下:

Public Property Get Products() As Products
Run Code Online (Sandbox Code Playgroud)

因此,您需要有一个Products类(非常类似于Workbook.WorksheetsWorkbook.Sheets属性都返回一个Sheets集合对象),该类封装了一个私有的模块级VBA.Collection字段(可能有键,但您不能访问或迭代集合的键)。

Products自定义集合类需要一个Item 默认属性(这个名字Item是一个约定); 该实现只是从私有封装中提取项目Collection

'@DefaultMember
Public Property Get Item(ByVal Index As Variant) As Product
    Set Item = ThePrivateCollection.Item(Index)
End Property
Run Code Online (Sandbox Code Playgroud)

如果您使用Rubberduck,此@DefaultMember注释/评论将触发有关注释和相应隐藏属性“不同步”的检查结果;右键单击该检查结果并选择“调整属性值”,让 Rubberduck 为您生成隐藏代码并处理烦人的导出/删除-编辑-重新导入循环。

否则,您需要手动编辑隐藏VB_UserMemId成员属性,使其成为类的默认成员

Public Property Get Item(ByVal Index As Variant) As Product
    Attribute Item.VB_UserMemId = 0
    Set Item = ThePrivateCollection.Item(Index)
End Property
Run Code Online (Sandbox Code Playgroud)

有了这个,MyBrand.Products("Shoes")就相当于MyBrand.Products.Item("Shoes").

也许您也想迭代集合中的所有产品?

For Each Product In MyBrand.Products
    Debug.Print Product.Name
Next
Run Code Online (Sandbox Code Playgroud)

为了做到这一点,您需要一个特殊的“枚举器”成员,它从封装的集合中转发枚举器:

'@Enumerator
Public Property Get NewEnum() As IUnknown
    Set NewEnum = ThePrivateCollection.[_NewEnum]
End Property
Run Code Online (Sandbox Code Playgroud)

同样,Rubberduck 注释极大地简化了此操作,但是 Rubberduck 所做的一切,如果您愿意,也可以手动执行:

Public Property Get NewEnum() As IUnknown
    Attribute NewEnum.VB_UserMemId = -4
    Set NewEnum = ThePrivateCollection.[_NewEnum]
End Sub
Run Code Online (Sandbox Code Playgroud)

现在For Each迭代适用于您的自定义对象集合!

如果 aLot不仅仅是一个String值(即实际的对象类型),那么Product该类也可以使用Lots自定义集合 - 但由于 aLot真的只是一个String值(或者是吗?),那么Product可以简单地封装 a Dictionary,并有一个Lots公开Items数组的属性:

Public Property Get Lots() As Variant
    Lots = ThePrivateLotsDictionary.Items
End Property
Run Code Online (Sandbox Code Playgroud)

请注意,这比使用 a 更简单Collection,因为对于一个集合,您需要迭代它并将每个项目复制到一个数组中,以便在不暴露集合本身的情况下返回这些项目(暴露Lots() As Collection使AddLot成员完全多余)。

至于Brands集合本身,请注意Tim Williams 的建议并使用Dictionary数据结构。

  • 当我第一次看到你的答案时,我必须设置一个提醒,以便在获得更多课程模块经验后回来。经过半个月的学习,我感觉自己对其中蕴含的智慧还只领悟了一半。感谢您的精彩回答,我必须在学习更多内容后再次回来。 (2认同)