Pass user input to new instance of class - vb.net

Alright, I have looked through 20 pages on here and can't find what I'm looking for... I've seen it in C# and other languages.. but not Visual Basic..
Say I have a class:
Public Class Cars
Private doors as integer
Private Liters as double
Private otherStuff as string
' more code'
end class
Say I also have a Form.. an inputForm we'll call it that has numerous textboxes for users to input these characteristics. The first textbox is labeled nameTextBox. Is there any way to assign the string value of that textbox as a new car?
something to the likes of..
dim nameTextBox.value as new car
??

The fields in your class are private, so they arent of much use - no other code will be able to see those values.
Public Class Car
Public Property Make As String
Public Property Model As String
Public Property Year As Integer
' something the class may use/need but doesnt expose
Private DealerCost As Decimal
' the constructor - called when you create a NEW car
Public Sub New(mk As String, md As String)
Make = mk
Model = md
End Sub
...
End Class
By specifying only a constructor which takes params, I am saying that you cannot create a new car without specifying those properties. If the constructor takes no params, then you can create an "empty" car object and set each property individually. You can do both - called overloading - so you can create a car with or without the Make and Model at the outset.
As Public Properties, other code can examine them to see what kind of car this is.
Dim myCar = New Car("Toyata", "Corolla")
myCar.Year = 2013
myCar.Color = Color.Blue
The text used of course can come from user input:
Dim myCar = New Car(tbMake.Text, tbModel.Text)
Dim yr As Int32
If Integer.TryParse(tbYear.Text, yr) Then
myCar.Year = yr
Else
' ToDo: scold user
End If

Related

Object Property Being Changed When It's Not Supposed To? (Vb.net)

I'll try to keep it simple, but this is making me almost rip my hair out as I do not understand why a certain property is being changed on one of my objects when I am not assigning it a change.
Sample class:
Public Class Person
Public Name As String
Public Age as UInteger
End Class
OK cool.
In my project:
Dim Me as New Person, You as New Person
Me.Name = "John"
You.Name = "Terry"
Me = You
You.Age = 32
So I assigned the Name properties to 'Me' and 'You' respectively as John & Terry. I then coped the properties of 'You' To 'Me'.
When I go change the Age Property of 'You' to 32. The property age of 'Me' ALSO gets changed to 32 even though I never assigned it a change.
A total head scratcher as I am not quite catching the error here?
If you want to create a new object with the same property values then you have no option but to create a new object and copy the property values. There are a number of ways that you can implement the details though.
The most obvious and most basic is to simply create a new object and copy the property values:
Public Class Person
Public Property Name As String
Public Property Age As UInteger
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson As New Person
secondPerson.Name = firstPerson.Name
secondPerson.Age = firstPerson.Age
The next option to consider is to build the functionality into the type itself. If you do this, the first option is to implement the ICloneable interface and use the MemberwiseClone method:
Public Class Person
Implements ICloneable
Public Property Name As String
Public Property Age As UInteger
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson = DirectCast(firstPerson.Clone(), Person)
The ICloneable.Clone method returns an Object reference, because it must work for any type, so it's not ideal. You might choose not to implement the interface and change the return type, or you could add your own Copy method and cast the result:
Public Class Person
Implements ICloneable
Public Property Name As String
Public Property Age As UInteger
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function
Public Function Copy() As Person
Return DirectCast(Clone(), Person)
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson = firstPerson.Copy()
That MemberwiseClone method creates a shallow copy of the current object, which means a direct copy of property value. That's fine for simple types like String and numeric types but may not be for more complex types. For instance, if you had a property that referred to a DataSet then the same property in the new object would refer to that same DataSet object, not a new DataSet containing the same data. The same goes for arrays and collections. If you don't want a shallow copy then you need to write your own code to explicitly create a deep copy in the specific way that you want, e.g.
Public Class Person
Public Property Name As String
Public Property Age As UInteger
Public ReadOnly Property Children As New List(Of Person)
Public Function Copy() As Person
Dim newPerson As New Person With {.Name = Name,
.Age = Age}
For Each child As Person In Children
newPerson.Children.Add(child.Copy())
Next
Return newPerson
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
firstPerson.Children.Add(New Person With {.Name = "Jim", .Age = 10})
firstPerson.Children.Add(New Person With {.Name = "Jane", .Age = 12})
Dim secondPerson = firstPerson.Copy()
The reason that's not done by default is that you could end up with a stack overflow. In this case, creating a copy of a Person object will also create a copy of the Person objects in its Children collection. If there is a circular reference there somewhere, i.e. two Person objects were in each other's Children collection then you would just keep creating copies until the system ran out of resources. You need to be very careful when creating deep copies in order to avoid such situations. You need to be very sure that you only go to the depth you need and that you catch any circular references and stop copying when you get back to the start of the chain.
Notice that this last example does require you to specify each property explicitly. It is possible to avoid this if you use Reflection but that is less efficient and, unless you have a ridiculous number of properties, a bit silly as the few minutes it takes to type out those properties should be a very minor annoyance.

VBA List of Custom Datastructures

One of the main problems in VBA are custom data structures and lists.
I have a loop which generates with each iteration multiple values.
So as an example:
Each loop iteration generates a string "name" an integer "price" and an integer "value".
In C# for example I'd create a class which can hold these three values and with each loop iteration I add the class object to a list.
How can I do the same thing in VBA if I want to store multiple sets of data when not knowing how many iterations the loop will have (I cant create an array with a fixed size)
Any ideas?
The approach I use very frequently is to use a class and a collection. I also tend to use an interface model to make things more flexible. An example would look something like this:
Class Module IFoo
Option Explicit
Public Sub Create(ByVal Name as String, ByVal ID as String)
End Property
Public Property Get Name() as String
End Property
Public Property Get ID() as String
End Property
This enforces the pattern I want for my Foo class.
Class Module Foo
Option Explicit
Private Type TFoo
Name as String
ID as String
End Type
Private this as TFoo
Implements IFoo
Private Sub IFoo_Create(ByVal Name as String, ByVal ID as String)
this.Name = Name
this.ID = Name
End Sub
Private Property Get IFoo_Name() as String
IFoo_Name = this.Name
End Property
Private Property Get IFoo_ID() as String
IFoo_ID = this.ID
End Property
We get intellisense from the Private Type TFoo : Private this as TFoo where the former defines the properties of our container, the latter exposes them privately. The Implements IFoo allows us to selectively expose properties. This also allows you to iterate a Collection using an IFoo instead of a Foo. Sounds pointless until you have an Employee and a Manager where IFoo_BaseRate changes depending on employee type.
Then in practice, we have something like this:
Code Module Bar
Public Sub CollectFoo()
Dim AllTheFoos as Collection
Set AllTheFoos = New Collection
While SomeCondition
Dim Foo as IFoo
Set Foo = New Foo
Foo.Create(Name, ID)
AllTheFoos.Add Foo
Loop
For each Foo in AllTheFoos
Debug.Print Foo.Name, Foo.ID
Next
End Sub
While the pattern is super simple once you learn it, you'll find that it is incredibly powerful and scalable if implemented properly. It also can dramatically reduce the amount of copypasta that exists within your code (and thus reduce debug time).
You can use classes in VBA as well as in C#: Class Module Step by Step or A Quick Guide to the VBA Class Module
And to to the problem with the array: you can create an array with dynamic size like this
'Method 1 : Using Dim
Dim arr1() 'Without Size
'somewhere later -> increase a size to 1
redim arr1(UBound(arr1) + 1)
You could create a class - but if all you want to do is hold three bits of data together, I would define a Type structure. It needs to be defines at the top of an ordinary module, after option explicit and before any subs
Type MyType
Name As String
Price As Integer
Value As Integer
End Type
And then to use it
Sub test()
Dim t As MyType
t.Name = "fred"
t.Price = 12
t.Value = 3
End Sub

Get value of a property with propertyinfo object

Is there a way to get value of a object properties with a propertyinfo object?
psudo code:
propertyinfoObject = Text
myobject.toCommand(propertyinfoObject)
The psudo code above should do the same as
myobject.Text
My goal is to create a simpel Properties form that will work on any object (Later I will use keywords to filter out what options I want the use to see).
My real code
Public Class PropertiesForm
Dim propertyInfoVar() As PropertyInfo
Dim Properties As New Form2
Dim listItem As New ListViewItem
Dim stringarray() As String
Public Sub New(ByRef sender As Object)
propertyInfoVar = sender.GetType().GetProperties()
For Each p In propertyInfoVar
stringarray = {p.Name.ToString, #INSERT VALUE SOMEHOW HERE#}
listItem = New ListViewItem(stringarray)
Properties.ListView1.Items.Add(listItem)
Next
Properties.Visible = True
End Sub
EDIT
Just use propertyGrid as suggested below!
The standard PropertyGrid already does all that for you. Filtering properties is not so obvious, here's how:
The control includes a BrowsableAttributes property which allows you to specify that only properties with the specified attribute value should be shown. You can use existing attributes, or custom ones. This is specifically for tagging visible props:
<AttributeUsage(AttributeTargets.Property)>
Public Class PropertyGridBrowsableAttribute
Inherits Attribute
Public Property Browsable As Boolean
Public Sub New(b As Boolean)
Browsable = b
End Sub
End Class
Apply it to an Employee class to hide pay rates or anything else:
Public Class Employee
<PropertyGridBrowsable(True)>
Public Property FirstName As String
...
<PropertyGridBrowsable(False)>
Public Property PayRate As Decimal
<PropertyGridBrowsable(False)>
Public Property NationalInsuranceNumber As String
Test code:
Dim emp As New Employee With {.Dept = EmpDept.Manager,
.FirstName = "Ziggy",
.PayRate = 568.98D,
...
.NationalInsuranceNumber = "1234567"
}
propGrid.BrowsableAttributes = New AttributeCollection(New PropertyGridBrowsableAttribute(True))
propGrid.SelectedObject = emp
BrowsableAttributes is a collection, so you can add several.

VB.NET Add row data to public class / public field of type List(Of T) Object reference not set to an instance of an object

I am trying to add rows to a public class that has public fields and am getting an error: Object reference not set to an instance of an object
Public Class EmailRecipient
EmailAddress As String = ""
FullName As String = ""
End Class
Public Class EmailDetails
Public FromEmail As String = ""
Public ToEmails As List(Of Emails) = nothing
End Class
Public Sub SetEmailDetails
'Populate EmailRecipient Class
Dim er As New EmailRecipient
er.EmailAddress = "rodney#norespect.com"
er.FullName = "Rodney Dangerfield"
'Populate EmailDetails Class
Dim ed As New EmailDetails
ed.FromEmail = "sender#danger.com" 'This works fine
ed.ToEmails.Add(er) 'Here error happens
End Sub
I'm guessing I need to create an instance of the EmailRecipient class before I can add an item to it.
Not sure how to do that with a Public Field in a Public Class??
It's been a rough day. I got up this morning, put a shirt on and a button fell off. I picked up my briefcase and the handle came off. I'm afraid to go to the bathroom.
Thanks for the help :-)
I'm guessing I need to create an instance of the EmailRecipient class
You already have an instance of the EmailRecipient class. That's your er variable. You actually have two errors here. First, you explicitly set ToEmails to Nothing:
Public ToEmails As List(Of Emails) = nothing
This means that your ToEmails variable is a Null Reference. It doesn't have an actual object yet.
The second issue is that you shouldn't get that excpetion, because this shouldn't even compile. You define ToEmails as a List(Of Emails), but tried to add an object of type "EmailRecepient" to it. That should be a compiler error. If it's not, you need to turn Option Strict or Option Infer back on.
So what you really need is an instance of a the List(Of EmailRecipient) type. Fix the bad line of code like this:
Public ToEmails As New List(Of EmailRecipient)

Pass arguments to Constructor in VBA

How can you construct objects passing arguments directly to your own classes?
Something like this:
Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
Not being able to do this is very annoying, and you end up with dirty solutions to work this around.
Here's a little trick I'm using lately and brings good results. I would like to share with those who have to fight often with VBA.
1.- Implement a public initiation subroutine in each of your custom classes. I call it InitiateProperties throughout all my classes. This method has to accept the arguments you would like to send to the constructor.
2.- Create a module called factory, and create a public function with the word "Create" plus the same name as the class, and the same incoming arguments as the constructor needs. This function has to instantiate your class, and call the initiation subroutine explained in point (1), passing the received arguments. Finally returned the instantiated and initiated method.
Example:
Let's say we have the custom class Employee. As the previous example, is has to be instantiated with name and age.
This is the InitiateProperties method. m_name and m_age are our private properties to be set.
Public Sub InitiateProperties(name as String, age as Integer)
m_name = name
m_age = age
End Sub
And now in the factory module:
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
And finally when you want to instantiate an employee
Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)
Especially useful when you have several classes. Just place a function for each in the module factory and instantiate just by calling factory.CreateClassA(arguments), factory.CreateClassB(other_arguments), etc.
EDIT
As stenci pointed out, you can do the same thing with a terser syntax by avoiding to create a local variable in the constructor functions. For instance the CreateEmployee function could be written like this:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Set CreateEmployee = new Employee
CreateEmployee.InitiateProperties name:=name, age:=age
End Function
Which is nicer.
I use one Factory module that contains one (or more) constructor per class which calls the Init member of each class.
For example a Point class:
Class Point
Private X, Y
Sub Init(X, Y)
Me.X = X
Me.Y = Y
End Sub
A Line class
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
And a Factory module:
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
One nice aspect of this approach is that makes it easy to use the factory functions inside expressions. For example it is possible to do something like:
D = Distance(NewPoint(10, 10), NewPoint(20, 20)
or:
D = NewPoint(10, 10).Distance(NewPoint(20, 20))
It's clean: the factory does very little and it does it consistently across all objects, just the creation and one Init call on each creator.
And it's fairly object oriented: the Init functions are defined inside the objects.
EDIT
I forgot to add that this allows me to create static methods. For example I can do something like (after making the parameters optional):
NewLine.DeleteAllLinesShorterThan 10
Unfortunately a new instance of the object is created every time, so any static variable will be lost after the execution. The collection of lines and any other static variable used in this pseudo-static method must be defined in a module.
When you export a class module and open the file in Notepad, you'll notice, near the top, a bunch of hidden attributes (the VBE doesn't display them, and doesn't expose functionality to tweak most of them either). One of them is VB_PredeclaredId:
Attribute VB_PredeclaredId = False
Set it to True, save, and re-import the module into your VBA project.
Classes with a PredeclaredId have a "global instance" that you get for free - exactly like UserForm modules (export a user form, you'll see its predeclaredId attribute is set to true).
A lot of people just happily use the predeclared instance to store state. That's wrong - it's like storing instance state in a static class!
Instead, you leverage that default instance to implement your factory method:
[Employee class]
'#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
With that, you can do this:
Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Employee.Create is working off the default instance, i.e. it's considered a member of the type, and invoked from the default instance only.
Problem is, this is also perfectly legal:
Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
And that sucks, because now you have a confusing API. You could use '#Description annotations / VB_Description attributes to document usage, but without Rubberduck there's nothing in the editor that shows you that information at the call sites.
Besides, the Property Let members are accessible, so your Employee instance is mutable:
empl.Name = "Jane" ' Johnny no more!
The trick is to make your class implement an interface that only exposes what needs to be exposed:
[IEmployee class]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
And now you make Employee implement IEmployee - the final class might look like this:
[Employee class]
'#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
Notice the Create method now returns the interface, and the interface doesn't expose the Property Let members? Now calling code can look like this:
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
And since the client code is written against the interface, the only members empl exposes are the members defined by the IEmployee interface, which means it doesn't see the Create method, nor the Self getter, nor any of the Property Let mutators: so instead of working with the "concrete" Employee class, the rest of the code can work with the "abstract" IEmployee interface, and enjoy an immutable, polymorphic object.
Using the trick
Attribute VB_PredeclaredId = True
I found another more compact way:
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
As you can see the New_ constructor is called to both create and set the private members of the class (like init) only problem is, if called on the non-static instance it will re-initialize the private member. but that can be avoided by setting a flag.
First, here is a very quick summary/comparison of the baseline approach and the top three answers.
The baseline approach: These are the basic ways to construct new instances of a class:
Dim newEmployee as Employee
Dim newLunch as Lunch
'==Very basic==
Set newEmployee = new Employee
newEmployee.Name = "Cam"
newEmployee.Age = 42
'==Use a method==
Set newLunch = new Lunch
newLunch.Construct employeeName:= "Cam" food:="Salad", drink:="Tea"
Above, Construct would be a sub in the Lunch class that assigns the parameter values to an object.
The issue is that even with a method, it took two lines, first to set the new object, and second to fill the parameters. It would be nice to do both in one line.
1) The Factory class (bgusach): Make a separate class ("Factory"), with methods to create instances of any other desired classes including set-up parameters.
Possible use:
Dim f as Factory 'a general Factory object
Dim newEmployee as Employee
Dim newLunch as Lunch
Set f = new Factory
Set newEmployee = f.CreateEmployee("Bob", 25)
Set newLunch = f.CreateLunch("Bob", "Sandwich", "Soda")
When you type "f." in the code window, after you Dim f as Factory, you see a menu of what it can create via Intellisense.
2) The Factory module (stenci): Same, but instead of a class, Factory can be a standard module.
Possible use:
Dim newEmployee as Employee
Dim newLunch as Lunch
Set newEmployee = CreateEmployee("Jan", 31) 'a function
Set newLunch = CreateLunch("Jan", "Pizza", "JuiceBox")
In other words, we just make a function outside the class to create new objects with parameters. This way, you don't have to create or refer to a Factory object. You als don't get the as-you-type intellisense from the general factory class.
3) The Global Instance (Mathieu Guindon): Here we return to using objects to create classes, but sticking to the class-to-be-made. If you modify the class module in an external text editor you can call class methods before creating an object.
Possible use:
Dim newEmployee as Employee
Dim newLunch as Lunch
Set newEmployee = newEmployee.MakeNew("Ace" 50)
Set newLunch = newLunch.MakeNew("Ace", "Burrito", "Water")
Here MakeNew is a function like CreateEmployee or CreateLunch in the general factory class, except that here it is in the class-to-be-made, and so we don't have to specify what class it will make.
This third approach has a fascinating "created from itself" appearance to it, permitted by the global instance.
Other ideas: Auto-instancing, a Clone method, or a Parent Collection Class
With auto instancing (Dim NewEmployee as new Employee, note the word "new"), you can achieve something similar to the global instance without the setup process:
Dim NewEmployee as new Employee
NewEmployee.Construct("Sam", 21)
With "new" in the Dim statement, the NewEmployee object is created as an implied pre-step to calling its method. Construct is a Sub in the Employee class, just like in the baseline approach.[1]
There are some issues with auto instancing; some hate it, a few defend it.2 To restrict auto-instancing to one proto-object, you could add a MakeNew function to the class as I used with the Global Instance approach, or revise it slightly as Clone:
Dim protoEmployee as new Employee 'with "new", if you like
'Add some new employees to a collection
Dim someNames() as Variant, someAges() as Variant
Dim someEmployees as Collection
someNames = array("Cam", "Bob", "Jan", "Ace")
someAges = array(23, 45, 30, 38)
set someEmployees = new Collection
for i = 0 to 3
someEmployees.Add protoEmployee.Clone(someNames(i), someAges(i))
next
Here, the Clone method could be set up with optional parameters Function Clone(optional employeeName, optional employeeAge)and use the properties of the calling object if none are supplied.
Even without auto-instancing, a MakeNew or Clone method within the class itself can create new objects in one line, once you create the proto-object. You could use auto-instancing for a general factory object in the same way, to save a line, or not.
Finally, you might want a parent class. A parent class could have methods to create new children with parameters (e.g., with Employees as a custom collection, set newEmployee = Employees.AddNew(Tom, 38)). For a lot of objects in Excel this is standard: you can't create a Worksheet or a Workbook except from its parent collection.
[1]One other adjustment relates to whether the Construct method is a Sub or a Function. If Construct is called from an object to fill in its own properties, it can be a Sub with no return value. However, if Construct returns Me after filling in the parameters, then the Factory methods/functions in the top 2 answers could leave the parameters to Construct. For example, using a factory class with this adjustment could go: Set Sue = Factory.NewEmployee.Construct("Sue", "50"), where NewEmployee is a method of Factory that returns a blank new Employee, but Construct is a method of Employee that assigns the parameters internally and returns Me.
Another approach
Say you create a class clsBitcoinPublicKey
In the class module create an ADDITIONAL subroutine, that acts as you would want the real constructor to behave. Below I have named it 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(...)
The only penalty is the extra call, but the advantage is that you can keep everything in the class module, and debugging becomes easier.
Why not this way:
In a class module »myClass« use Public Sub Init(myArguments) instead of Private Sub Class_Initialize()
Instancing:
Dim myInstance As New myClass: myInstance.Init myArguments