Thursday, August 16, 2012
VB6 IsMissing() and optional arguments
In VB6/VBA the IsMissing function is used to test for optional arguments passed to user-defined function procedures as variants. Default values were not required for optional arguments; if no value was passed then IsMissing returned true. It was also possible to specify a default value of a primitive type to pass to the Variant argument.
The important point is that IsMissing only work for optional argurment of Variant Type.
If the optional argument is not of variant type, you should not use the IsMissing() function.
In this case always supply a default value, even if the compiler does not require it.
Monday, August 13, 2012
VBA / VB6 Interface implementation
Few months ago I blogged about interface implementation in VB6. You can find the post here
However, I discovered that the code had a memory leak caused by a circulare reference.
You can find a solution here.
The Keyword Implements in VBA/VB6 allows you to do Interface Implementation, which in turns allows for Polymorphism. This is indeed a great capability of the VB6 language that is often overlooked.
While the VB6 version of interface implementation is not so elegant as the one from .NET environment, it still allows you to implement most of the pattern of the GoF books
I will show you a very easy example on how to use it in VBA.
The Idea is to create an interface called IInstrumet with just one property "Id", and have a Security class that implements its interface. You could also have a Fund, Porftolio or a Security Class that implements this interface. This wil allow for polymorphism.
Dim inst as IInstrument
Dim sec as Security
Dim fn as Fund
Set sec = new Security
Set fn = new Fund
Set inst = sec
Set inst = fn
As you can see, bot a security and a fund can be assigned to an Instruments object!
We first define a Class called IInstrument. The code is here
We now create a new Class called Security that Implements the IInstrument one. This is a bit more tricky.
Once we implement an Interface, the Class that implements it in VB6 will declare those method as Private like that.
Private Property Get IInstrument_Id() As String
IInstrument_Id = mId
End Property
This is a kind of unsual behaviour, because if I know that the Security class implements the IInstrument interface, I expect to have access to the same methods and property made available by the interface obejcts. For this reason, I usually expose as public member the same properties and functions that are available in the interface. When I implement them, I delegate the job to the interface method
3) I implement the interface methods as I normally do. a declare a module level variable mId
Private Property Get IInstrument_Id() As String
IInstrument_Id = mId
End Property
Private Property Let IInstrument_Id(value As String)
mId =value
End Property
4) I crate public properties / methods mirroring the interface delegating their implementation to the
mInstrument object
Public Property Get Id() As String
Id =IInstrument_Id
End Property
Public Property Let Id(value As String)
IInstrument_Id = value
End Property
Here you can find the Security Class Code
However, I discovered that the code had a memory leak caused by a circulare reference.
You can find a solution here.
The Keyword Implements in VBA/VB6 allows you to do Interface Implementation, which in turns allows for Polymorphism. This is indeed a great capability of the VB6 language that is often overlooked.
While the VB6 version of interface implementation is not so elegant as the one from .NET environment, it still allows you to implement most of the pattern of the GoF books
I will show you a very easy example on how to use it in VBA.
The Idea is to create an interface called IInstrumet with just one property "Id", and have a Security class that implements its interface. You could also have a Fund, Porftolio or a Security Class that implements this interface. This wil allow for polymorphism.
Dim inst as IInstrument
Dim sec as Security
Dim fn as Fund
Set sec = new Security
Set fn = new Fund
Set inst = sec
Set inst = fn
As you can see, bot a security and a fund can be assigned to an Instruments object!
We first define a Class called IInstrument. The code is here
'This is an Interface for the Generic Financial Instrument Public Property Get Id() As String 'Only Signature End Property Public Property Let Id(value As String) 'Only Signature End Property
We now create a new Class called Security that Implements the IInstrument one. This is a bit more tricky.
Once we implement an Interface, the Class that implements it in VB6 will declare those method as Private like that.
Private Property Get IInstrument_Id() As String
IInstrument_Id = mId
End Property
This is a kind of unsual behaviour, because if I know that the Security class implements the IInstrument interface, I expect to have access to the same methods and property made available by the interface obejcts. For this reason, I usually expose as public member the same properties and functions that are available in the interface. When I implement them, I delegate the job to the interface method
3) I implement the interface methods as I normally do. a declare a module level variable mId
Private Property Get IInstrument_Id() As String
IInstrument_Id = mId
End Property
Private Property Let IInstrument_Id(value As String)
mId =value
End Property
4) I crate public properties / methods mirroring the interface delegating their implementation to the
mInstrument object
Public Property Get Id() As String
Id =IInstrument_Id
End Property
Public Property Let Id(value As String)
IInstrument_Id = value
End Property
Here you can find the Security Class Code
Implements IInstrument Private mId As String Public Ticker as String Private Sub Class_Initialize() End Sub Private Property Get IInstrument_Id() As String IInstrument_Id = mId End Property Private Property Let IInstrument_Id(value As String) mId = value End Property 'Public Interface Public Property Get Id() As String Id = IInstrument_Id End Property Public Property Let Id(value As String) IInstrument_Id = value End PropertyWe can now test the code
Sub TestSecurity() Dim Sec1 As Security Dim Inst As IInstrument Dim Sec2 As Security Set Sec1 = New Security Sec1.Id = 10 Sec1.Ticker = "MXEU" Set Inst = Sec1 'Upcast: A Security in an Instruments Debug.Print Inst.Id 'DownCast, this should have been done explicit, but VBA does not support CType. 'VB6 does. So instead of CType(Inst, "Security") we can do If TypeName(Inst) = "Security" Then Set Sec2 = Inst End If Set Sec2 = Inst Debug.Print Sec2.Id Debug.Print Sec2.Ticker End Sub
Thursday, August 9, 2012
VBA Strongly typed collections
An Excel File with the sample code can be found here
In this blog post I will show you how to create a Strongly Type Collection Class wich has both an Item as default property and for wich we can use the For Each Loop.
First we create a very simple Class named Person with just three properties Name, Surname and Date of Birth
Then we can create the Collection Class called People
Property Get Item(Index As Variant) As Parameter
Attribute Item.VB_UserMemId = 0
Set Item = m_Collection.Item(Index)
End Property
Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
Attribute NewEnum.VB_MemberFlags = "40"
Set NewEnum = Me.mCollection.[_NewEnum]
End Property
Note that the Attribute directive must be just below the functions signatures, otherwise the code will not work.
Now the above all looks "normal" except for the addition of the three "Attribute" Lines.
In the Item Property the line "Attribute Item.VB_UserMemId = 0" makes it the default property.
In the NewEnum, the "Attribute NewEnum.VB_UserMemId = -4" makes it the Default Enumeration Property (I'm sure you recognize the "-4" part.)
The Attribute NewEnum.VB_MemberFlags = "40" is to make the Enumerator a Hidden property, but, technically, this is not recognized in VBA, so it will be visible in IntelliSense, but I don't find that a big deal.
The solution is to
(1) Make your Class
(2) SAVE
(3) Export the Class
(4) Remove the Class (steps 3 and 4 can be combined into one, as it asks you if you wish to "Export" when you right-click and choose "Remove")
(5) Manually add the Attribute Lines as shown above
(6) Re-Import the edited Class
An easier way, as one of my reader pointed out is
1) To write the Attributes directives directly on the VBA Ide. You will get a syntax error. Ignore it
2) Export the Class
3) Remove the Class
4) Reinport it again.
The Attribute will be in the .cls file, but they will not be visible (you can add the Attribute NewEnum.VB_MemberFlags = "40" line if you wish -- it won't hurt anything -- but it won't be recognized in VBA, it will just be quietly ignored. So there's no reason to bother doing this, really.)
As you know, editing the code thereafter has some propensity to lose these properties (even in VB6) and so this may have to be repeated occassionally. (A bit of a pain.) The alternative is to create your class 100% within VB6 and then import it into your VBA Project. Or, even better, make it in VB6, debugg it, get it running 100%, compile to DLL and then add this DLL to your references. This last concept is probably the most solid, but there could be deployment issues as your DLL now has to be correctly Registered on the Client machine. Not that this is a big problem, but it's not as easy as distributing a VBA Project...
This is the Code you can use to test the Class
In this blog post I will show you how to create a Strongly Type Collection Class wich has both an Item as default property and for wich we can use the For Each Loop.
First we create a very simple Class named Person with just three properties Name, Surname and Date of Birth
Option Explicit Dim mName As String Dim mSurname As String Dim mDateofBirth As Date Public Property Get Name() As String Name = mName End Property Public Property Let Name(strName As String) mName = strName End Property Public Property Get Surname() As String Surname = mSurname End Property Public Property Let Surname(strSurname As String) mSurname = strSurname End Property Public Property Get DateOfBirth() As Date DateOfBirth = mDateofBirth End Property Public Property Let DateOfBirth(dteDateofBirth As Date) mDateofBirth = dteDateofBirth End Property
Then we can create the Collection Class called People
Option Explicit 'This is going to be a stroingly type Collection Private mCol As Collection Private Sub Class_Initialize() Set mCol = New Collection End Sub Private Sub Class_Terminate() Set mCol = Nothing End Sub Property Get Item(Index As Variant) As Person 'Attribute Item.VB_UserMemId = 0 'This Attribute makes Item the default property Set Item = mCol.Item(Index) End Property Property Get NewEnum() As IUnknown 'Attribute NewEnum.VB_UserMemId = -4 'Attribute NewEnum.VB_MemberFlags = "40" 'The first Attribute makes it the Default Enumerator Property 'The second Attribute makes the Enumerator a hidden property. This does not work with the VBA intellisense 'This Routine Get the Enumerator for the Collection. 'To get this to work you must add two attributes Set NewEnum = mCol.[_NewEnum] End Property Public Sub Add(Item As Person, Optional key As Variant) Call mCol.Add(Item, key) End Sub Public Function Count() As Long Count = mCol.Count End Function Public Sub Remove(Index As Variant) mCol.Remove (Index) End SubThere are few Attributes that you cannot see in the VBA IDE. If you export a file from one of your VB6 procedures and view it, you'll notice that some Attributes, not visible while editing your code, are added to the top of the routine(s). The two properties in question will look something like this:
Property Get Item(Index As Variant) As Parameter
Attribute Item.VB_UserMemId = 0
Set Item = m_Collection.Item(Index)
End Property
Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
Attribute NewEnum.VB_MemberFlags = "40"
Set NewEnum = Me.mCollection.[_NewEnum]
End Property
Note that the Attribute directive must be just below the functions signatures, otherwise the code will not work.
Now the above all looks "normal" except for the addition of the three "Attribute" Lines.
In the Item Property the line "Attribute Item.VB_UserMemId = 0" makes it the default property.
In the NewEnum, the "Attribute NewEnum.VB_UserMemId = -4" makes it the Default Enumeration Property (I'm sure you recognize the "-4" part.)
The Attribute NewEnum.VB_MemberFlags = "40" is to make the Enumerator a Hidden property, but, technically, this is not recognized in VBA, so it will be visible in IntelliSense, but I don't find that a big deal.
The solution is to
(1) Make your Class
(2) SAVE
(3) Export the Class
(4) Remove the Class (steps 3 and 4 can be combined into one, as it asks you if you wish to "Export" when you right-click and choose "Remove")
(5) Manually add the Attribute Lines as shown above
(6) Re-Import the edited Class
An easier way, as one of my reader pointed out is
1) To write the Attributes directives directly on the VBA Ide. You will get a syntax error. Ignore it
2) Export the Class
3) Remove the Class
4) Reinport it again.
The Attribute will be in the .cls file, but they will not be visible (you can add the Attribute NewEnum.VB_MemberFlags = "40" line if you wish -- it won't hurt anything -- but it won't be recognized in VBA, it will just be quietly ignored. So there's no reason to bother doing this, really.)
As you know, editing the code thereafter has some propensity to lose these properties (even in VB6) and so this may have to be repeated occassionally. (A bit of a pain.) The alternative is to create your class 100% within VB6 and then import it into your VBA Project. Or, even better, make it in VB6, debugg it, get it running 100%, compile to DLL and then add this DLL to your references. This last concept is probably the most solid, but there could be deployment issues as your DLL now has to be correctly Registered on the Client machine. Not that this is a big problem, but it's not as easy as distributing a VBA Project...
This is the Code you can use to test the Class
Option Explicit Sub prova() Dim Employee As Person Dim Director As Person Dim Team As People Dim p As Person Dim i As LongSet Employee = New Person Employee.DateOfBirth = "10 Jan 1974" Employee.Name = "Mario" Employee.Surname = "Rossi"Set Director = New Person Director.DateOfBirth = "10 Mar 1970" Director.Name = "Giulia" Director.Surname = "Verdi"Set Team = New People Call Team.Add(Employee) Call Team.Add(Director) For i = 1 To Team.Count Debug.Print Team(i).Name Next i For Each p In Team Debug.Print p.Name NextEnd Sub
Labels:
collection,
default,
enumerator,
property,
strongly,
typed,
vba
Subscribe to:
Posts (Atom)