I am very new to VBS, but I am not able to implement even the simplest things, as it seems. I want to have a class which holds an array in a private member. Since I want to "inject" the array I tried to implement a "setter-method" using the Let functionality.
Class CPhase
Private m_AllowedTasks()
Public Property Let AllowedTasks(p_AllowedTasks)
m_AllowedTasks = p_AllowedTasks
End Property
Private Sub Class_Initialize()
ReDim m_AllowedTasks(0)
End Sub
End Class
This class is used as follows:
Dim allowed
allowed = Array("task1", "task2")
Dim phase
Set phase = New CPhase
phase.AllowedTasks = allowed
This results in a "Microsoft VBScript runtime error (...) : Type mismatch" in the Let-method. I also tried using different combinations of "ByVal", "ByRef", but since having absolutely no experience with VBS I couldn't find a solution. So what am I doing wrong?
Any hints or links to helpful ressources are very appreciated!
Thanks!
The culprit is
Private m_AllowedTasks()
which creates an abomination - a fixed array of no size. Just remove the ().
Private m_AllowedTasks
to create an (empty) Variant that may be set=let to an useful (redim-able) array.
Related
I'm working with a rather complex problem in VBA and I decided to structure it in different classes.
One of the classes containd an array and I need to modify the component individualy outside the class.
The problem can de reduced to the definition of the class
Public a As Variant
Private Sub Class_Initialize()
ReDim a(5) As Double
' Set some value
a(1) = 20
End Sub
And a sub to access the data:
Sub Test()
Dim b As New DummyTest
b.a(4) = 7
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
The result on the debug window is 20 0. I've checked this result with an inspection and confirm that the property a cannot be modified from the exterior of the class.
Funny part is that the property can be modified if replaced by other array, for example:
Sub Test2()
Dim b As New DummyTest
b.a = Array(1, 2, 3, 4, 5)
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
Behaves as expected. I'm confused as element access is allowed inside the class context and in any other situation, but in this specific case it doesn't wotk. There is no error msg, it simply refuses to change the content of the array.
I'll find work around solutions but missing this way to access data is being a really PITA.
Hope you can help me with this issue.
Thank you in advance!
Your "property" is breaking encapsulation, which pretty much defeats its entire purpose.
Public a As Variant
From a public interface standpoint, this is a read-write property (if you added a new class module with Implements DummyTest you'd have to add Property Get and Property Let members for it). From the class' standpoint, it's a public instance field.
When a class encapsulates an array, a collection, a dictionary, or any other data structure, the last thing you want is for that data structure to be publicly exposed, with anyone anywhere being able to just overwrite the class' entire internal state as it pleases - you're not getting any of the advantages of classes that way.
First step to proper encapsulation is to make the instance field Private. Next step is to expose actual Property accessors. You want the array data to be writable? Expose an indexed Property Let member for it.
Private a As Variant
Private Sub Class_Initialize()
ReDim a(0 To 10)
End Sub
Public Property Get Value(ByVal index As Long) As Variant
Value = a(index)
End Property
Public Property Let Value(ByVal index As Long, ByVal newvalue As Variant)
a(index) = newvalue
End Property
This code behaves exactly as advertised:
Public Sub Test()
Dim b As DummyTest
Set b = New DummyTest
b.Value(4) = 7
Debug.Print b.Value(1)
Debug.Print b.Value(4)
End Sub
Note that because the array is encapsulated, that client code is absolutely unable to re-assign the array itself: the array is abstracted away and only accessible through the means exposed by the class.
Reference variable in VBA must be declared, so that we know what type of object we are referring to
i.e. Dim b As DummyTest ' No new required in this step
Before using a declared reference to store information create an instance of the Class.
i.e. Set b = New DummyTest
This might seem confusing but is actually very logical. Because b is a Class the Dim statement sets up b to hold the address of a DummyTest object. At the time of the Dim statement no object has been created (no memory allocated) so the address of b is nothing (no value or null). The set statement tells VBA to create an instance of your DummyTest class (allocate the memory it needs) and puts the address of the instance (memory) into variable b. Then because VBA knows that b points to an instance of a class you never see the address of b when you use it, just the data to which b is pointing. You can get the the address b is holding using the function Objptr but this is only something you would do in very special/specific circumstances
Let's say I have an interface ITest1 defined:
Option Explicit
Sub it1Test(cTest As ITest1)
End Sub
And then I built a class CTest1 that implements it:
Option Explicit
Implements ITest1
Private Sub ITest1_it1Test(cTest As ITest1)
End Sub
Private Sub Test1()
Dim cT1 As CTest1
cT1 = New CTest1
ITest1_it1Test cT1
End Sub
If I try compiling this code everything works perfectly fine, which in general is what I expect.
But then in my not so simple world, rather than passing a single element I want to pass a whole array.
So my new interface ITest2 looks like this
Option Explicit
Sub it2Test(cTest() As ITest2)
End Sub
And a class CTest2 implementing it:
Option Explicit
Implements ITest1
Private Sub ITest1_it1Test(cTest() As ITest1)
End Sub
Private Sub Test2()
Dim cT2(1 To 1) As CTest2
cT2(1) = New CTest2
ITest2_it2Test cT2
End Sub
This time I am not able to compile with an error:
Compile error:
ByRef argument type mismatch
with highlighted the cT2 parameter of function ITest2_it2Test as the source of error.
Is there any way to pass an array of class implementing an interface if the function expects array of type of that interface. In other words does it make sense to use interface as a type of array parameter?
I'm aware that it is possible to use Variant as an array type but it undermines plenty of benefits that I have thanks to the use of interface.
I can't find anything related to such specific case. I have checked number of pages about ways to implement interfaces in general (from MS docs to CPearson page). I can't also find anything here on Stack Overflow. I have checked all 78 questions tagged vba and excel with keywords interface and array and some of the suggested questions.
Being new to programming and having it introduced to me through my course I've been doing tasks in and out of College in Visual Basic using Visual Studio to make games and other little applications. However in my most recent project i've experienced a problem in one of my arrays I have never come across before. The specific error im getting comes up with this when highlighted:
Data type(s) of the type parameter(s) in method 'Public Shared Overloads Function IndexOf(Of T)(array As T(), value As T) As Integer' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error.
I have all the arrays (18, for 9 different buttons each containing a question and an answer variant) running in form load as it was the only way I could get the arrays to work with a randomiser and show the question in the button. Then the array index is being found and created in the submit answer button. I'll give some snippets for further context below from various points where the array is being referenced :)
I created this to make the string global but I have an inkling this is wrong?
Public Class Form3
Public QBox1 As String
Public QBoxA1 As String
This is where the arrays are and how i've structured them
Public Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BOX 1 (TOP LEFT)
Dim QBox1() As String = {"√81", "4x6", "16/4", "21+18", "81-23"}
Dim QBoxA1() As Integer = {"9", "24", "4", "39", "58"}
And finally this is within the submit answer button where the error is
If QBoxA1.Contains(txt_AnswerAttempt.Text) Then
Dim question_index = Array.IndexOf(QBox1, btn_Q1.Text)
Dim answer_index = Array.IndexOf(QBoxA1, answerAttemptDisplay.Text)
If question_index = answer_index Then
MsgBox("Correct Answer!")
Else
MsgBox("Wrong Answer!")
End If
End If
Sorry for information overload, I wanted to be thorough right off the bat! Cheers for giving this a read if you made it to the end xD
Your declaration isn't right. Since you want an array, don't declare it as a single string. It should be:
Public QBox1() As String
then in the load event, don't re-declare it with a Dim statement. Just re-populate it:
QBox1 = {"√81", "4x6", "16/4", "21+18", "81-23"}
I am very new to VBScript, and not entirely sure if what I am doing is right.
I want to create a structure to hold onto a string and then an array of strings. The array of string will be dynamic, as I do not know how many entries are going to be in that list.
I have the following:
Class ExportMappings
Private _process_definition
Private _export_mappings : Set _export_mappings = CreateObject("System.Collection.ArrayList")
Public Property Let ProcessDefinition(procDef)
_process_definition= procDef
End Property
Public Property Get ProcessDefinition()
ProcessDefinition = _process_definition
End Property
Public Property Let ExportMappings(export)
_export_mappings = export
End Property
Public Sub AddMapping(map)
_export_mappings.Add map
End Sub
End Class
First, I am not sure if I declared the _export_mapping array properly.
Secondly, I do not know if I need a constructor to initialize my _export_mappings to an initial size. If so, I do not know how I would do that.
Lastly, my get and set methods for ExportMapping, I am not sure if that will work.
I would try to run it through a debugger, but the software I am using does not have the best debugger, and usually gives me a very vague description of what is wrong.
First things first:
VBScript variable names can't start with _; you can 'legalize' invalid names by putting them in [], but for starters I wouldn't do this
Code in Classes is allowed in methods only; your Private _export_mappings : Set _export_mappings = CreateObject("System.Collection.ArrayList") is invalid
After these changes, your code should compile. If you add code showing how you'd like to use this class, I'm willing to talk about second things (maybe tomorrow).
Its been a while since I've worked with ASP, but I'm getting a Type mismatch error on what seems to be a simple assignment statement. Can anyone shed some light on why this might be happening.
This works, but when I try to foreach an unassigned Questions block I get an Object not a collection error
Class Survey
public ID
public Title
public Questions
End Class
Sub Test()
Dim oSurvey
Set oSurvey = new Survey
Dim aQuestions(2)
Set aQuestions(0) = new Question
' Other aQuestions assignments
oSurvey.Questions = aQuestions
End Sub
Alternately If I setup questions as a dynamic array then the assignment fails with a type mismatch error.
Class Survey
public ID
public Title
public Questions()
End Class
Sub Test()
Dim oSurvey
Set oSurvey = new Survey
Dim aQuestions(2)
Set aQuestions(0) = new Question
' Other aQuestions assignments
' Throws a Type mismatch error here
oSurvey.Questions = aQuestions
End Sub
Any thoughts?
To answer your question as to what is actually going on.
when I try to foreach an unassigned
Questions block I get an Object not a
collection error
For Each enumerates a set of variants from the source variable, it does this by acquiring an IEnumVARIANT. If the source variable holds an object it is expected to have an implementation of this interface. If it is an array VBScript creates an implementation dynamically and it can only do this if the array has been dimensioned. Anything else in the source variable (such as Empty in this case) will result in an error.
then the assignment fails with a type mismatch error.
The left hand side of an assignment operation must always be a variant. Hence its not possible copy the contents of one dynamic array to another via a simple assignment.
Your first approach is reasonably sound but you need a way to represent an empty array without crashing out a For Each. You can use this little trick:-
Function EmptyArray
EmptyArray = Split("", " ")
End Function
Class Survey
public ID
public Title
public Questions
Private Sub Class_Initialize
Questions = EmptyArray
End Sub
End Class
Now if you try to For Each the Questions before it has been assigned a real array the for each will do nothing as expected. Also if you use UBound(Questions) + 1 to get the count of questions that will still be accurate since UBound(EmptyArray) is -1.
If I try to paste the same code into VBA (excel or word), it doesn't compile.
It shows error on line public Questions() saying
---------------------------
Microsoft Visual Basic
---------------------------
Compile error:
Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules
---------------------------
OK Help
---------------------------
In the first example, you haven't defined it as an array (it is a variant & hence can be assigned any value).
Update: I tested this in ASP.NET not realizing the question was about classic ASP. I've modified the code below to work with classic ASP, though I haven't been able to test it yet:
Class Question
...
End Class
Class Survey
Public ID
Public Title
Public Questions As Question()
End Class
Sub Test()
Dim oSurvey As New Survey
Dim aQuestions(0 To 2) As Question
Set aQuestions(0) = New Question
...
Set oSurvey.Questions = aQuestions
End Sub
In your first example, Survey.Questions isn't a collection; in your second, it is an array of Type Variant.
So I ended up sticking with the Array declaration. Additionally, When I tried to ReDim the array like so I got an error.
ReDim oSurvey.Questions(2)
So I created a sub routine to ReDim the Array, and this worked.
Class Survey
public ID
public Title
public Questions()
sub ReDimQuestions(count)
ReDim Questions(count)
end sub
End Class
Sub Test()
Dim oSurvey
Set oSurvey = new Survey
oSurvey.ReDimQuestions 2
Set oSurvey.Questions(0) = new Question
' Other aQuestions assignments
End Sub