使用数组进行Excel VBA编程:传递或不传递它们?

时间:2010-01-22 22:48:20

标签: arrays excel vba

2 个答案:

答案 0 :(得分:11)

你已经提供了很多细节,但是如果不看一些代码,仍然很难理解究竟发生了什么。在您的问题中,我可以确定您交织的至少4个重要主题:制造,数据访问,VBA和编码最佳实践。由于你的问题范围很大,我很难准确地说出你在问什么。无论哪种方式,我感谢您尝试在VBA中编写更好的代码。

我很难准确理解你打算用数组做什么。你说:

  

缺点是我在多个程序中使用了大量相同的基本信息,这要求我将它加载到具有多次微小差异的数组中。

我不确定你的意思。您是否使用数组来表示从数据库中检索的一行数据?如果是这样,您可以考虑使用类模块而不是通常的“宏”模块。这些将允许您使用完整的对象而不是值数组(或引用,视情况而定)。课程需要更多的工作来设置和使用,但它们使您的代码更易于使用,并将极大地帮助您分割代码。

正如用户Emtucifor已经指出的那样,可能存在诸如ADO Recordset个对象(可能需要安装Access ...不确定)等对象可能会有很大帮助。或者您可以创建自己的。

这是一个很长的例子,说明如何使用类可以帮助你。虽然这个例子很冗长,但它将向您展示面向对象编程的一些原则如何真正帮助您清理代码。

在VBA编辑器中,转到Insert > Class Module。在“属性”窗口(默认情况下在屏幕的左下角)中,将模块的名称更改为WorkLogItem。将以下代码添加到类中:

Option Explicit

Private pTaskID As Long
Private pPersonName As String
Private pHoursWorked As Double

Public Property Get TaskID() As Long
    TaskID = pTaskID
End Property

Public Property Let TaskID(lTaskID As Long)
    pTaskID = lTaskID
End Property

Public Property Get PersonName() As String
    PersonName = pPersonName
End Property

Public Property Let PersonName(lPersonName As String)
    pPersonName = lPersonName
End Property

Public Property Get HoursWorked() As Double
    HoursWorked = pHoursWorked
End Property

Public Property Let HoursWorked(lHoursWorked As Double)
    pHoursWorked = lHoursWorked
End Property

上面的代码将为我们提供一个强类型对象,该对象特定于我们正在使用的数据。当您使用多维数组来存储数据时,您的代码类似于:arr(1,1)是ID,arr(1,2)是PersonName,arr(1,3)是HoursWorked。使用该语法,很难知道什么是什么。假设您仍然将对象加载到数组中,而是使用我们在上面创建的WorkLogItem。这个名字,您可以arr(1).PersonName来获取此人的姓名。这使您的代码更容易阅读。

让我们继续这个例子。我们将尝试使用collection

,而不是将对象存储在数组中

接下来,添加一个新的类模块并将其命名为ProcessWorkLog。将以下代码放在那里:

Option Explicit

Private pWorkLogItems As Collection

Public Property Get WorkLogItems() As Collection
    Set WorkLogItems = pWorkLogItems
End Property

Public Property Set WorkLogItems(lWorkLogItem As Collection)
    Set pWorkLogItems = lWorkLogItem
End Property

Function GetHoursWorked(strPersonName As String) As Double
    On Error GoTo Handle_Errors
    Dim wli As WorkLogItem
    Dim doubleTotal As Double
    doubleTotal = 0
    For Each wli In WorkLogItems
        If strPersonName = wli.PersonName Then
            doubleTotal = doubleTotal + wli.HoursWorked
        End If
    Next wli

Exit_Here:
    GetHoursWorked = doubleTotal
        Exit Function

Handle_Errors:
        'You will probably want to catch the error that will '
        'occur if WorkLogItems has not been set '
        Resume Exit_Here


End Function

上述课程将用于与WorkLogItem的集体“做某事”。最初,我们只是将其设置为计算总工作小时数。让我们测试一下我们编写的代码。创建一个新模块(这次不是类模块;只是一个“常规”模块)。将以下代码粘贴到模块中:

Option Explicit

Function PopulateArray() As Collection
    Dim clnWlis As Collection
    Dim wli As WorkLogItem
    'Put some data in the collection'
    Set clnWlis = New Collection

    Set wli = New WorkLogItem
    wli.TaskID = 1
    wli.PersonName = "Fred"
    wli.HoursWorked = 4.5
    clnWlis.Add wli

    Set wli = New WorkLogItem
    wli.TaskID = 2
    wli.PersonName = "Sally"
    wli.HoursWorked = 3
    clnWlis.Add wli

    Set wli = New WorkLogItem
    wli.TaskID = 3
    wli.PersonName = "Fred"
    wli.HoursWorked = 2.5
    clnWlis.Add wli

    Set PopulateArray = clnWlis
End Function

Sub TestGetHoursWorked()
    Dim pwl As ProcessWorkLog
    Dim arrWli() As WorkLogItem
    Set pwl = New ProcessWorkLog
    Set pwl.WorkLogItems = PopulateArray()
    Debug.Print pwl.GetHoursWorked("Fred")

End Sub

在上面的代码中,PopulateArray()只创建了WorkLogItem的集合。在您的实际代码中,您可以创建类来解析Excel工作表或数据对象以填充集合或数组。

TestGetHoursWorked()代码简单地演示了如何使用类。您注意到ProcessWorkLog被实例化为对象。实例化后,WorkLogItem的集合将成为pwl对象的一部分。你在行Set pwl.WorkLogItems = PopulateArray()中注意到了这一点。接下来,我们简单地调用我们编写的函数,该函数作用于集合WorkLogItems

为什么这有用?

假设您的数据发生了变化,并且您想要添加新方法。假设您的WorkLogItem现在包含HoursOnBreak的字段,并且您想添加一种新方法来计算它。

您需要做的就是向WorkLogItem添加一个属性,如下所示:

Private pHoursOnBreak As Double

Public Property Get HoursOnBreak() As Double
    HoursOnBreak = pHoursOnBreak
End Property

Public Property Let HoursOnBreak(lHoursOnBreak As Double)
    pHoursOnBreak = lHoursOnBreak
End Property

当然,您需要更改填充集合的方法(我使用的示例方法是PopulateArray(),但您可能应该有一个单独的类)。然后,您只需将新方法添加到ProcessWorkLog课程中:

Function GetHoursOnBreak(strPersonName As String) As Double
     'Code to get hours on break
End Function

现在,如果我们想要更新我们的TestGetHoursWorked()方法以返回GetHoursOnBreak的结果,我们只需要添加以下行:

    Debug.Print pwl.GetHoursOnBreak("Fred")

如果传入了表示数据的值数组,则必须在代码中找到使用数组的每个位置,然后相应地更新它。如果您使用类(及其实例化对象),则可以更轻松地更新代码以使用更改。此外,当您允许以多种方式使用类时(可能一个函数只需要4个对象属性而另一个函数需要6个),它们仍然可以引用同一个对象。这使您无法为不同类型的函数使用多个数组。

如需进一步阅读,我高度建议您获取VBA Developer's Handbook, 2nd edition的副本。这本书充满了很好的例子和最佳实践以及大量的示例代码。如果你为VBA投入大量时间进行一个认真的项目,那么值得花些时间来研究这本书。

答案 1 :(得分:3)

这听起来像是Excel,数组不是你正在做的工作的最佳工具。如果您能解释一下您正在使用的数据类型以及您正在做的事情,那么这将有助于提供更好的答案。尽可能详细地说明您对数据进行的操作类型以及输入和输出的内容。

我将提供一些我认为会对你有帮助的亮点,然后可以编辑我的答案,以便在我收到你的回复时更加完整,所以我有更多时间来充实你的东西。

  • 有一个对象可以自然地处理您正在使用的记录类型对象,称为Recordset。在VBA编辑器中,转到工具 - >引用并添加Microsoft ActiveX Data Objects 2.X Library(您计算机上最高的一个)。您可以声明ADODB.Recordset类型的对象,然后执行Recordset.Fields.Append向其添加字段,然后。打开它,最后添加.AddNew,设置字段值和.Update。这是一个在程序中作为输入或输出参数传递的自然对象。它具有自然的遍历和定位功能(.Eof,.Bof,。AbsolutePosition,.MoveNext,.MoveFirst,.MovePrevious)并支持搜索和过滤(.Filter =“Field ='abc'”,.Find等)。

  • 我不建议使用公共变量,但如果不了解你在做什么我就不能在这里给你很好的建议。

  • 我也会避免一个大程序。代码应该分解为可重用的功能单元,它们只做一件事,其名称基本上是自我记录的。

  • 如果您想提高代码的性能,请在运行时随机点击ctrl-break并进入代码。然后按Ctrl-L查看调用堆栈。记下每次列表中的内容。如果任何项目在大多数时间出现,那么它就是瓶颈,是您应该花时间尝试优化它的地方。但是,我建议您尝试优化所拥有的内容,直到您做出更高级别的决策(例如是否将切换到记录集)。

我真的需要更多信息来帮助你。

如果您有兴趣,我会编写一些演示代码,以显示Recordset对象的用途。使用Recordset.GetRows或.GetString将Recordset中的数据插入Excel范围非常容易(虽然可能需要进行一些数组转换,但这并不难)。

更新:如果您的目标是加快您的流程,那么在做任何事情之前,我认为最好是掌握最耗费时间的知识。请你点击ctrl-break大约10次,每次记下调用堆栈,然后告诉我调用堆栈中最常见的项目是什么?

在更新单元格格式的速度方面,这是我的经验:

  1. 合并是您可能做的最慢的操作。尽可能避免使用它。使用“选择中心”是另一种选择。另一个是不合并,而是使用正确的大小调整,边框,单元格背景颜色和关闭整个工作簿的网格线的组合。

  2. 将边框或其他格式设置应用到最大可能的一次,而不是逐个细胞等许多小事。例如,如果大多数单元格具有所有边框但有些单元格没有,则将所有边框应用于整个范围,并在循环期间删除不需要的边框。即使这样,也要尝试做整行和更大的范围。

  3. 保存已应用边框和格式的模板文件。假设您在其中添加了一行,其中包含特定部分的格式。在一个步骤中,将该行复制到该部分需要的行数,例如20行,并且它们将具有相同的格式。复制行比逐个单元格应用格式要快得多。

  4. 另外,我不会自动使用课程。虽然面向对象很棒,但我自己也做了(哎呀,我前几天只为一些层次结构建立了8个类,所以我可以在需要时轻松地展示它的部分),实际上它可能会更慢。类中的一组简单公共变量比使用getter和setter更快。用户定义的Type甚至比类快,但是你可以遇到试图在类中传递UDT的陷阱(它们必须在非类的公共模块中声明,即使这样它们也会产生问题)。

    埃里克