VBA传两个参数报错

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VBA传两个参数报错相关的知识,希望对你有一定的参考价值。

参考技术A 首先打开excle软件,进入VBA的编程界面,创建一个子程序test1,在VBA编程函数中怎样传递参数。然后在子程序中定义两个变量num1和num2,在VBA编程函数中怎样传递参数。,紧接着对这两个变量赋值。在VBA编程函数中怎样传递参数。调用sumup函数,并传入num1和num2这两个参数,并将返回的结果输出来。在VBA编程函数中怎样传递参数。把num1和num2这两个变量的值打印出来,看看这两个值被传入另外一个函数内部之后,值有没有改变。
在VBA编程函数中怎样传递参数。创建函数sumup,在sumup函数里面有两个参数,在参数前面用了ByVal,表示参数是按照值传递的方式进行,默认是按照地址进行传递的。

将参数传递给VBA中的构造函数

【中文标题】将参数传递给VBA中的构造函数【英文标题】:Pass arguments to Constructor in VBA 【发布时间】:2013-02-19 20:56:24 【问题描述】:

您如何构造直接将参数传递给您自己的类的对象?

类似这样的:

Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)

无法做到这一点很烦人,最终你会得到肮脏的解决方案来解决这个问题。

【问题讨论】:

codereview.stackexchange.com/questions/67825/… 为了不变性,可以使用类中的私有初始化和工厂:Private VBA Class Initializer called from Factory 跟进上述评论。 GitHub 上的 repo 现在支持 Private Class 初始化程序。方法称为RedirectInstance,需要从私有函数调用。结合类工厂,实现了不变性。 【参考方案1】:

这是我最近使用的一个小技巧,效果很好。想和那些经常用VBA打架的人分享一下。

1.- 在您的每个自定义类中实现一个公共启动子例程。我在所有课程中都将其称为 InitiateProperties。此方法必须接受您要发送给构造函数的参数。

2.- 创建一个名为 factory 的模块,并创建一个公共函数,其中包含单词“Create”加上与类相同的名称,以及与构造函数所需的相同的传入参数。此函数必须实例化您的类,并调用第 (1) 点中解释的启动子例程,并传递接收到的参数。最终返回了实例化和初始化的方法。

例子:

假设我们有自定义类 Employee。和前面的例子一样,is 必须用 name 和 age 实例化。

这是 InitiateProperties 方法。 m_name 和 m_age 是我们要设置的私有属性。

Public Sub InitiateProperties(name as String, age as Integer)

    m_name = name
    m_age = age

End Sub

现在在工厂模块中:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Dim employee_obj As Employee
    Set employee_obj = new Employee

    employee_obj.InitiateProperties name:=name, age:=age
    set CreateEmployee = employee_obj

End Function

最后当你想实例化一个员工时

Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)

当你有几个类时特别有用。只需在模块工厂中为每个模块放置一个函数并通过调用 factory.CreateClassA(arguments)factory.CreateClassB(other_arguments) 等进行实例化。

编辑

正如 stenci 指出的那样,您可以通过避免在构造函数中创建局部变量来使用更简洁的语法来做同样的事情。例如 CreateEmployee 函数可以这样写:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Set CreateEmployee = new Employee
    CreateEmployee.InitiateProperties name:=name, age:=age

End Function

哪个更好。

【讨论】:

不错的解决方案!虽然我可能会将其重命名为 factory.CreateEmployee 以减少歧义...... 工厂模块相对于每个类中的 Construct 方法有什么好处。所以你会打电话给Set employee_obj = New Employee,然后是employee_obj.Construct "Johnny", 89,构造的东西发生在课堂上。只是好奇。 嗨,我看到了一些好处。它们可能有些重叠。首先,您以标准方式使用构造函数,就像在任何普通的 OOP 语言中一样,这提高了清晰度。然后,每次实例化一个对象时,保存该行来初始化对象,这样可以减少编写量,然后不能忘记初始化对象,最后在过程中减少一个概念,从而降低复杂性。 您可以使用私有变量对其进行跟踪。你可以定义Class_Initialize,然后在那里定义一个变量m_initialized = false。当您输入InitiateProperties 时,检查m_initialized,如果它为假,则继续,最后将其设置为真。如果为真,则引发错误或不执行任何操作。如果再次调用 InitiateProperties 方法,它将为 true,并且对象的状态不会改变。 很抱歉把它从死里唤醒,但最后一段是错误的,那是不是“更好”的代码。将函数的返回机制(分配给标识符)视为声明的局部变量是误导和混淆充其量,而不是“更好”(看起来像递归调用,不它?)。如果您想要更简洁的语法,请利用我的回答中所示的模块属性。作为奖励,您失去了 Single-Responsibility-Principle-takes-a-beating “工厂包”模块,该模块负责创建几乎所有事物及其母亲的实例,并且不可避免地会在任何体面的大小中变得一团糟项目。【参考方案2】:

我使用一个Factory 模块,每个类包含一个(或多个)构造函数,它调用每个类的Init 成员。

例如Point 类:

Class Point
Private X, Y
Sub Init(X, Y)
  Me.X = X
  Me.Y = Y
End Sub

一个Line

Class Line
Private P1, P2
Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
  If P1 Is Nothing Then
    Set Me.P1 = NewPoint(X1, Y1)
    Set Me.P2 = NewPoint(X2, Y2)
  Else
    Set Me.P1 = P1
    Set Me.P2 = P2
  End If
End Sub

还有一个Factory 模块:

Module Factory
Function NewPoint(X, Y)
  Set NewPoint = New Point
  NewPoint.Init X, Y
End Function

Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
  Set NewLine = New Line
  NewLine.Init P1, P2, X1, Y1, X2, Y2
End Function

Function NewLinePt(P1, P2)
  Set NewLinePt = New Line
  NewLinePt.Init P1:=P1, P2:=P2
End Function

Function NewLineXY(X1, Y1, X2, Y2)
  Set NewLineXY = New Line
  NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2
End Function

这种方法的一个很好的方面是可以很容易地在表达式中使用工厂函数。例如,可以执行以下操作:

D = Distance(NewPoint(10, 10), NewPoint(20, 20)

或:

D = NewPoint(10, 10).Distance(NewPoint(20, 20))

它很干净:工厂做的很少,它在所有对象中始终如一地做,只是创建和一个Init 调用每个creator

而且它是相当面向对象的:Init 函数是在对象内部定义的。

编辑

我忘了补充一点,这允许我创建静态方法。例如,我可以做类似的事情(在使参数可选之后):

NewLine.DeleteAllLinesShorterThan 10

不幸的是,每次都会创建一个新的对象实例,因此任何静态变量在执行后都会丢失。此伪静态方法中使用的行集合和任何其他静态变量必须在模块中定义。

【讨论】:

这比选择的答案更干净。 我上次玩VBA已经很久了,但是... 1:你如何从Factory的子例程中获取构造对象? Sub 的定义没有返回值。 2:即使我遗漏了这一点,您的 Factory 所做的事情与我的几乎相同:创建一个对象(我分两步完成,您的语法显然更短),调用 Init/InitiateProperties 方法,在我的例子中,显式返回。 @ikaros45 他们应该是Function,而不是Sub,我编辑了帖子,谢谢。是的,它和你的一样,只是随着类的数量和每个类的“构造函数”数量的增加,它的组织方式更容易管理(在我看来)。 是的,组织完全一样,但我同意你的方式更简洁。这意味着相同,但您为每个构造函数节省了两行代码,这很好。如果你不介意,我会用你的语法更新我的代码。【参考方案3】:

另一种方法

假设你创建了一个类 clsBitcoinPublicKey

在类模块中创建一个额外的子例程,它的行为就像你希望真正的构造函数表现一样。下面我将其命名为 ConstructorAdjunct。

Public Sub ConstructorAdjunct(ByVal ...)

 ...

End Sub

From the calling module, you use an additional statement

Dim loPublicKey AS clsBitcoinPublicKey

Set loPublicKey = New clsBitcoinPublicKey

Call loPublicKey.ConstructorAdjunct(...)

唯一的惩罚是额外的调用,但好处是可以将所有内容保留在类模块中,调试变得更容易。

【讨论】:

除非我忽略了某些东西,否则这就像每次实例化任何对象时手动调用我的“InitiateProperties”,这是我一开始想避免的。【参考方案4】:

当您导出一个类模块并在记事本中打开文件时,您会注意到在顶部附近有一堆隐藏的属性(VBE 不显示它们,并且不公开调整其中大部分的功能任何一个)。其中之一是VB_PredeclaredId

Attribute VB_PredeclaredId = False

将其设置为True,保存并将模块重新导入到您的 VBA 项目中。

带有PredeclaredId 的类有一个您可以免费获得的“全局实例” - 与UserForm 模块完全一样(导出用户表单,您会看到它的 predeclaredId 属性设置为 true)。

很多人只是乐于使用预先声明的实例来存储状态。这是错误的 - 这就像将实例状态存储在静态类中!

相反,您可以利用该默认实例来实现您的工厂方法:

[Employee类]

'@PredeclaredId
Option Explicit

Private Type TEmployee
    Name As String
    Age As Integer
End Type

Private this As TEmployee

Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
    With New Employee
        .Name = emplName
        .Age = emplAge
        Set Create = .Self 'returns the newly created instance
    End With
End Function

Public Property Get Self() As Employee
    Set Self = Me
End Property

Public Property Get Name() As String
    Name = this.Name
End Property

Public Property Let Name(ByVal value As String)
    this.Name = value
End Property

Public Property Get Age() As String
    Age = this.Age
End Property

Public Property Let Age(ByVal value As String)
    this.Age = value
End Property

有了它,你可以这样做:

Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)

Employee.Create 正在处理默认实例,即它被视为类型的成员,并且仅从默认实例调用。

问题是,这也是完全合法的:

Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)

这很糟糕,因为现在你有一个令人困惑的 API。您可以使用 '@Description annotations / VB_Description 属性来记录使用情况,但如果没有 Rubberduck,编辑器中将无法在呼叫站点向您显示该信息。

此外,Property Let 成员是可访问的,因此您的 Employee 实例是可变的

empl.Name = "Jane" ' Johnny no more!

诀窍是让你的类实现一个接口,它只公开需要公开的内容:

[IEmployee类]

Option Explicit

Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property

现在你让Employee 实现 IEmployee - 最后的类可能看起来像这样:

[Employee类]

'@PredeclaredId
Option Explicit
Implements IEmployee

Private Type TEmployee
    Name As String
    Age As Integer
End Type

Private this As TEmployee

Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
    With New Employee
        .Name = emplName
        .Age = emplAge
        Set Create = .Self 'returns the newly created instance
    End With
End Function

Public Property Get Self() As IEmployee
    Set Self = Me
End Property

Public Property Get Name() As String
    Name = this.Name
End Property

Public Property Let Name(ByVal value As String)
    this.Name = value
End Property

Public Property Get Age() As String
    Age = this.Age
End Property

Public Property Let Age(ByVal value As String)
    this.Age = value
End Property

Private Property Get IEmployee_Name() As String
    IEmployee_Name = Name
End Property

Private Property Get IEmployee_Age() As Integer
    IEmployee_Age = Age
End Property

注意Create 方法现在返回接口,而接口公开Property Let 成员?现在调用代码可以是这样的:

Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)

由于客户端代码是针对接口编写的,empl 暴露的唯一成员是IEmployee 接口定义的成员,这意味着它看不到Create 方法,也看不到Self getter,也不是任何Property Let mutators:因此,其余代码可以使用“抽象”IEmployee 接口,而不是使用“具体”Employee 类,并享受不可变的多态对象。

【讨论】:

注意:immutable 并不是真正可以实现的;该实例可以访问其字段,并且可以很好地改变它们的值。但这比将Property Let 暴露给外界(或者更糟的是,公共领域!)要好。 调用代码不会是 Dim empl as Employee,因为 Employee Class 实现 IEmployee 否则运行时错误与您编写它的方式有关 @Jose Dim empl As IEmployee 工作精确因为Implements IEmployee 为什么我得到Variable not Defined for Employee.Create @Matheiu Guindon - 并不是要发送垃圾邮件,但我在将近 3 个月后重新访问这篇文章。从那以后,我一直在 OOP 上一遍又一遍地阅读你的橡皮鸭博客,现在这个答案对我来说完全有意义。我不敢相信我在上面的 cmets 中提出的问题。【参考方案5】:

使用技巧

Attribute VB_PredeclaredId = True

我发现了另一种更紧凑的方式:

Option Explicit
Option Base 0
Option Compare Binary

Private v_cBox As ComboBox

'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
  If Me Is ComboBoxExt_c Then
    Set New_ = New ComboBoxExt_c
    Call New_.New_(cBox)
  Else
    Set v_cBox = cBox
  End If
End Function

如您所见,调用 New_ 构造函数来创建和设置类的私有成员(如 init),唯一的问题是,如果在非静态实例上调用,它将重新初始化私有成员。但这可以通过设置标志来避免。

【讨论】:

【参考方案6】:

为什么不这样:

    在类模块 »myClass« 中使用 Public Sub Init(myArguments) 而不是 Private Sub Class_Initialize() 实例化: Dim myInstance As New myClass: myInstance.Init myArguments

【讨论】:

请用英文写下你的答案,Stack Overflow is an English site.

以上是关于VBA传两个参数报错的主要内容,如果未能解决你的问题,请参考以下文章

asp function 传递两个以上参数报错的问题

如何使用 vba 将参数传递给 asp.net web api?

两个jsp之间传参数乱码

VBA中如何获取一个表格的行数和列数

将参数传递给查询访问 VBA

Excel VBA - 将参数参数传递给 Sub