Search This Blog

Showing posts with label vba. Show all posts
Showing posts with label vba. Show all posts

Monday, October 15, 2012

Property Set, Let, Get


In VBA when we define the properties of an object we have a Let, Set and Get operators.
We use the Get operator to retrieve the value of a property
Whe use the Set operator to set the value of an Object
We use the Let operator to set the value of a variable (not an object : string, double, enumeration...)

The use of the Set operator, force you to use the Set function when setting the value of an object property of type object.


Option Explicit

Private Const cMODULE_NAME As String = "House"
Private mAddress As String
Private mDoors As Collection



Public Property Get Address() As String
  
   Address = mAddress
   
End Property

Public Property Let Address(value As String)
   
   mAddress = value
   
End Property

Public Property Get Doors() As Collection
   
   Set Doors = mDoors
  
End Property

Public Property Set Doors(value As Collection)
   Set mDoors = value
End Property


Buttons and spreadsheet duplicaton with Excel VBA

If you want to add Button on an excel spreadsheet you have two choice

1) Active-x buttons
2) Form buttons


I you plan to duplicate a spreadsheet whic contains buttons using VBA Code, than you must use
Form buttons not Active-x ones.
If you use Active-x buttions you might get a VBA run time errors, which now on top of my head I don't rememeber.

So the tip here is
Use Form buttons if you plan to duplicat using VBA code to duplicate excel spread-sheets that contains them.

Using implements behind an Excel worksheet function

Since an excel worksheet is represented by a class module in vba, you might be tempted, as I was, to use the implements keywords behind a worksheet.
This would allow you to use polymorphically an Excel worksheet and could open-up differ possibilities.
Howev this is my advice

DO NOT USE IMPLEMENTS BEHING A WORKSHEET

I have noticed that despites the code compiles, the overall worksheet becomes unstable and tend to crash!
In additon the TypeOf function applied to the worksheet object that use the implements keyword, does not always behaves as you would expect.
Ex:
if you write on top of an excel worksheet module

Implements IEngine


Some time the test TypeOf sht is IEngine will return false even if it is implementing the interface.



Strongly typed dictionary collection in VBA

In this post, I will show you how to build a strongly type dictionary and how to loop through its elements with a For Each Loop.

The Generic Dictionary Object can be found in the "Microsoft Scripting Runtime" library.
This can be used to store any type of variables or object. However, it is usually very useful to wrap it up so that you can create your own strongly typed dictionary.
As you will see from the code below, a very simple way to loop through the collection of elements in a dictionary is to loop through its keys. The variable key must be variant type.
For Each key In emps.Keys
   Set emp = emps(key)
   Debug.Print emp.Name
Next

The first class is a strongly typed dictionary of Employee object, called Employees.
A usual to make the .Item property to be the default property you need to add the

Attribute Item.VB_UserMmeId = 0

just below the definition of the Get Item property

Option Explicit

Private Const cMODULE_NAME As String = "Employees"Private mDic As Dictionary


Private Sub Class_Initialize()
  Set mDic = New Dictionary
End Sub
Private Sub Class_Terminate()
  Set mDic = Nothing
End Sub

Public Sub Add(key As Variant, Item As Employee)
  
    Call mDic.Add(key, Item)
  
End Sub

Public Property Get Item(key As Variant) As Employee
   'Attribute Item.VB_UserMemId = 0
   'This Attribute makes Item the default property
   'In VBA, uncomment the first line. Export, Remove and import the file again. To make it work

   Set Item = mDic.Item(key)
       
End Property

Public Function count() As Long
   count = mDic.count
End Function

Public Function Exists(key As Variant) As Boolean
  Exists = mDic.Exists(key)
End Function

Public Function items() As Variant
 items = mDic.items
End Function

Public Function Remove(key As Variant)
  mDic.Remove (key)
End Function

Public Function RemoveAll()
  mDic.RemoveAll
End Function

Public Function Keys() As Variant
  Keys = mDic.Keys
End Function


This is the the Employee Class


Option Explicit

Private Const cMODULE_NAME As String = "Employee"
Private mIdentifier As Long
Private mName As String
Private mAge As Long

Public Property Get Identifier() As Long
  Identifier = mIdentifier
End Property

Public Property Let Identifier(value As Long)
    mIdentifier = value
End Property

Public Property Get Name() As String
   Name = mName
End Property

Public Property Let Name(value As String)
   mName = value
End Property

Public Property Get Age() As Long
  Age = mAge
End Property

Public Property Let Age(value As Long)
  mAge = value
End Property

This is the Sub to test the Code


Sub TestCollection()

Dim emp As Employee
Dim emps As Employees
Dim key As VariantSet emps = New Employees
Set emp = New Employee
emp.Identifier = 1
emp.Name = "Mario"
emp.Age = 34
Call emps.Add(emp.Identifier, emp)

Set emp = New Employee
emp.Identifier = 2
emp.Name = "Gino"
emp.Age = 12
Call emps.Add(emp.Identifier, emp)

For Each key In emps.Keys
   Set emp = emps(key)
   Debug.Print emp.Name
NextEnd Sub

Environ. A useful function to get environment infos

The Environ function is a pretty useful VBA function that gives you back many important info about the pc.
If you copy and paste this code snippet, you will see in the immediate window the full list of parameter to use and what they return.

One of the most useful are

USERNAME
APPDATA
CommonProgramFiles



Public Sub EnvironParameters()
    
    Dim nCount As Integer
    nCount = 0
    nCount = nCount + 1
    Do Until Environ(nCount) = ""
        Debug.Print Environ(nCount)
        nCount = nCount + 1
    Loop

   
End Sub

This is the complete list of parameters

ALLUSERSPROFILE

APPDATA
CommonProgramFiles
COMPUTERNAME
ComSpec
FP_NO_HOST_CHECK
HOMEDRIVE
HOMEPATH
HOMESHARE
LOGONSERVER
NUMBER_OF_PROCESSORS
OS
Path
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
ProgramFiles
PSModulePath
SESSIONNAME
SystemDrive
SystemRoot
TEMP
TMP
TNS_ADMIN
UATDATA
USERDNSDOMAIN
USERDOMAIN
USERNAME
USERPROFILE
VS90COMNTOOLS
WecVersionForRosebud.224
windir

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

'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 Property

We 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

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 Sub
There 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

Saturday, June 30, 2012

VBA Error Handling

In my previous post I showed the difference between

Break on All Errors
Break in Class module
Break on unhandled errors

After looking at each of them we came to the conclusion that we should really use Break on unhandled errors as our default option. See my post
We this option on, however we need to set up o more sofisticated approach to make our life easier.
I looked into the problem and the best approach I could find is the one described in details in the book,
Professional Excel Developers, Chapter 15, VBA Error Handling.
The Error Handling system described there are two: the functin return value and the re-throw method.
I will not go into the details of the two system in this post, I will just add few comments of mine and present you the main ideas.
To start with, The main Vba keyword to deal with Error handling are

1) The object Err
2) On Error Goto Label
3) On Error Resume Next
4) Resume / Resume Next / Resume Label
5) On Error Goto 0

The Err object is a global object whose property are filled by Vba as an error occurs.
Err.Number, Err.Source, Err.Descripton and Err.Raise are by far the most important ones.

Each time we meet a Exit Sub, Exit Fucntion, Exit Property, End Sub, End Function, End Property, Resume and On Error statement the property of the object error are reset. So some time we want to be carefull and store then into some variables.


An Error Handler is a labeled section of a procedure that will run as an error occur.

The Only way into this part of the code is an error, the only way out of this code should be a resume statement. You can see an example here.

Private Sub MySub()
    On Error Goto ErrorHandler  
   'Some code goes here
  
ExitProc:
  Exit Sub
ErrorHandler:
  'Clean up code goes here
  if CentralErrorHandler("Mymodule","MySub") Then
     Stop
     Resume
  else
     Go to ExitProc
  End if

Exit Sub


The second importan principle is the Single Exi Point. Eache time we write a procedure, we need to make sure that there is a single exit point. In this example is ExitProc.

The call to the CentralErrorHandler funcion happens only when we cannot deal with the error within the code, so an exception must be raised. In my personal implementation I actually changed the name of the function from CentraErrorHandler to Exception.LogMe, or if you want Exception.Inizialize.

The CentralErrorHandler function will be responsible for
1) Log erros to a txt log file
2) Activate or deactivate the Debug mode
3) Show a message to the User is we are at an entry point or in Debug mode
4) Re-raise the error is we are not at an entry point or we are not in Debug mode

The call looks like



Public Function CentralErrorHandler(module,proc,entryPoint,showMessage) as boolean

 module and proc tells the CentralErrorHandler what it the source of the error. In this case MyModule:MySub

entryPoint tells it if we are at an entry point.
showMessage tells it if we need a message displayed.


The CentralErroHandler function looks like

Public Function CentralErrorHandler(module,proc,entrypoint,showmessage) as boolean

  'Store the variable of the Global Error message
   Static errMsg as string


   errNum = Err.Num
   errSource = Err.Source
   errDes = Err.Description
   
   'We cannot allow errorn in the CentralErrorHandler
   On Error Resume Next

   errFullSource = module & ":" & proc 
   errLogTxt = errFullSource & " " &  Err.Num & " " & Err.Des
   
   if len(errMsg) = 0 Then errMsg = Err.Description
 
  'Log the errLogTxt Error into a text file

   if entryPoint OR DebugMode then
      if showMessage Then msgbox(errMsg)
      errMsg = vbNullString  
   else
      On Error Goto 0 
      Err.Raise errNum,  errFullSource, errMsg
      
   end if

End Function


The idea is
1) We store first the property of the Err Object, otherwise they will be reset by the call to Resume
2) We create the new source code and txt to be logged
3) We log the error to the file. We could add: if ToBeLog Then SaveToFile()
4) the errMsg is Static, which means that we will show the original message
5) If we are in DebugMode or at an EntyPoint show a message an reset the string
6) Otherwise re-raise the error

DebugMode is a boolean costant that tells the compiler if we are in DegubMode of not. We can define in at the module level that contains the global error handler.

In few words: when we call the CentralErrorHanlder and we are at an entry point or Debugmode is true a msgbox is shown and the program stops and the errMsg string is cleared. (see the example above on how to call it)
If the debug mode is false and we are not at entry point, a message is re-thrown, with the original Err.Num and Err.Description, but a new Source: MySub:MyModule.

What is an entryPoint?
An entry point is a point from which the user can start execution: menu button, worksheet events...

 For the System to work we need
1) Any Entry point procedure must never call another entry point procedure. If two of them need to run the same code, we can move the code out to a non-entry level procedure
If this happens, we have that the the entry point procedure called, will show a message rather than raising the error up to the caller.

A special case are the Excel User define Functions, which I still need to made my mind up how to treat them.

If we set an Excel UDF as EntryPoint = False and DebugMode = False than an error is re-thrown, so an Excel UDF must have EntryPoint = True

If we set for an Excel UDF EntryPoint = True then as there is an error a msgbox will be diplayed. This is not compliant from what you would expect for an Excel UDF, and image what happens if we had to run hundreds of them.

So we can have Excel UDF EntryPoint = True, showMessage = False.
This gets better, no message anymore.
But if we have 200,000 calls with an error we will log it 200,000 time in the txtfiles, whis is kind of inefficient.
So we could have

ExcelUDF EntryPoint = True, showMessage = False, LogTxt = False

This is getting better, but as you see we have nearly turned off all the Central handling facilities!
No message, no error re-thrown, no txt log. This begs the queston do we need a CentralErrorHandling at all for an Excel UDF?

Lastly if we define an ExcelUDF EntryPoint = true, than we cannot call it from any other part of the code, which is pretty limiting.
So what we can do it to move the code from the entry point to another internal function such that


Public Function MyUDFFunc() as Variant
   MyUDFFunc = MyUDFFuncInternal()
End Function



So that we can set MyUDFFuncInternal EntryPoint = False, in such a case the msgbox will be shown only in Debugmode.    EntryPoint = False, showMessage = True , LogTxt = False

Then MyUDFFunc instead does not need any handler at all.
If we are in DebugMode = True, all the debugging will happen in MyUDFFuncInternal, which will show a message and stop
If we are in DebugMode = False, any error will be logged by MyUDFFuncInternal, whill will no show any message because DebugMode = False, EntryPoint = False. If MyUDFFuncInternal is successfull it will pass the value up to MyUDFFunc. If MyUDFFuncInternal will re-throw an error, MyUDFFunc will just show #N/A, becasue it will exit from execution straightaway.

This could be the best solution: All the Excel UDF don't have any CentralErrorHandler at all, they just delegate the work at some internal fucntion, which wil full support the CentralErrorHandler approach.
This will allow us to reuse them in code easily.
I am still not completely convinced that this is the best approach though.

UPDATE:
After a quick chat with the author of Dailydose of excel, I came to realize that he does not use the Centra Error Handling either for Excel UDF. So my suggested soluiton is the best way I suggest to go.
For Excel UDF no central error Handling. We delegate their functon to some Internal function that implements the CentralErrorHandling Approach. We can set for this functin ErrorLog = False, to prevent to log 100,000 or more calls to failing functions




Thursday, June 28, 2012

VBA and VB6 Debugging Options

In this blog post I will explore the VBA Debugging options.
If you go to
Tools - Options - General

You will see the Error Trapping Options

1) Break on All Errors
2) Break in Class Modlue
3) Bread on Unhandled Errors


Depeding on whether you have an error hanler active or not, or if you call the a class of modue function you will get different behavious. Let's test them out.

1) Break on all errors. 

      Caller is a Sub of Function
          It stops at all errors as soon as they occur: ex division by zero or Err.Raise

      Caller is an Excel UDF.
         The code will NOT STOP. It will just end execution at the point where the error is caused
         Excel will Return #VALUE!


2) Break in Class Module

     Caller is a Sub or Function
          It stops only on Unhandled errors. If it meets an Err.Raise in a Class module it will stop
          in any case.

    Caller is an Excel UDF
          It terminate only on Unhandled errors. If it meets an Err.Raise in a Class modue it will raise
          Err.num 440, irrespective or the error number raised. VERY STRANGE
 
3) Break on Unhandled Errors
      Caller is a Sub or Functin or UDF.
      It stops only on Unhandler errors.


As you can see Opton number 3 is the one that gives the most consistency, followed by option number 1 and
finally option number 2.
I would recommend to use always "Break on Unhandled Errors" and switch to any of the other two options only if you are debugging difficult code.
Option 2 is interesting especially when you are developing an ActiveX component and you want to stop the debugger in the class.

Again, use "Break on Unhandled Errors" and you will save a lot of time trying to put up with the inconsistency between on how the debbuger behaves in case you are using an Excel UDF or just simply a sub or function.







Friday, April 27, 2012

Interface Implementation in VBA and VB6

WARNING THIS CODE HAS A MEMORY LEAK: check UPDATE HERE

I have realized that setting Set mInstruments = Me in the Class inizialize method create a circular reference and so a memory leak. The new code is much better.

The Keyworkd Implements in VBA/VB6 allows you to Interface Implementation in VB6, which in turns allows for Polymorfism. This is indeed a great capabilites on 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 some nice pattern like the strategy or the abstract facotory one.
I will show you a very easy example on how to use it in VBA."
The Idea is to create an interface calle IInstrumets 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 its interface. This wil allow for polymorfism.

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(strId 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 code the Class using this rules
1) In the Class that implements the interface I declare at Class level a private object of the interface type

      Private mInstrument As IInstrument



2) In the constructor I assing the Obj to this instance. This will allow me to call the interface methods of my class

  Private Sub Class_Initialize()
    'I need to Access the IInstruments Methods
    Set mInstrument = Me
End Sub

3) I implement the interface methods as I normally do.

Private Property Get IInstrument_Id() As String

    IInstrument_Id = mId  
End Property

Private Property Let IInstrument_Id(strId As String)
   mId = strId
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 = mInstrument.Id
End Property

Public Property Let Id(strId As String)
   mInstrument.Id = strId
End Property

Here you can find the Security Class Code


Implements IInstrument

Private mId As String
Private mInstrument As IInstrument

Public Ticker As String

Private Sub Class_Initialize()
  'I need to Access the IInstruments Methods
  Set mInstrument = Me
End Sub

Private Property Get IInstrument_Id() As String
       IInstrument_Id = mId
End Property


Private Property Let IInstrument_Id(strId As String)
  mId = strId
End Property


'Public Interface

Public Property Get Id() As String
     Id = mInstrument.Id
End Property

Public Property Let Id(strId As String)
  mInstrument.Id = strId
End Property

We 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

VB6 and VBA Enumerations

An Enumeration in VBA/VB6 is a special type of long Variable.
A great resource can be found here on cpearson.com

Enum Position
  [_First] = -1
  RelativeDynamic = 0
  RelativeStatic = 1
  Absolute = 2
  [_Last] = 3
End Enum


Enum FactorEngine
  [_First] = -1
  AA = 0
  [_Last] = 1
End Enum

  
The code above specify two kind of Enumerations, Position and FactorEngine.
The [_First] and [_Last] are not necessary but they can be used to validate the Enumerated variables.
The _ makes the Enumrated variable hidden to the intellisense, while the [ makes it a valid character for the VB6 interpreter.
This is an example on how to Validate Enumerations


Sub TestEnum()
Dim Fac As FactorEngine
Dim i As Long
Dim IsValid As Boolean

Fac = AA
IsValid = False

For i = FactorEngine.[_First] To FactorEngine.[_Last]

  If Fac = i Then
      IsValid = True
      Exit For
  End If
Next i

If IsValid = True Then
   Debug.Print Fac & " Is a valid Engine"
Else
   Debug.Print Fac & " Is NOT a valid Engine"
End If


End Sub

Wednesday, April 25, 2012

How to Create a Strongly typed Collection in Vba

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

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 Sub
There 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

Wednesday, January 4, 2012

Matlab Excel Link from VBA configuration

To create macros that use Excel Link functions, you must first configure Excel to reference the functions from the Excel Link add-in. From the Visual Basic environment pull down the Insert menu and select Module.When the Module page opens, pull down the Tools menu and select References.... In the References window, check the box for EXCLLINK.XLA and click OK. You may have to use Browse to find the EXCLLINK.XLA file.

If you use MLGetMatrix in a macro subroutine, enter MatlabRequest on the line after MLGetMatrix. MatlabRequest initializes internal Excel Link variables and enables MLGetMatrix to function in a subroutine. For example,

Sub Get_RangeA()
  MLGetMatrix "A", "RangeA"
  MatlabRequest

End Sub

Do not include MatlabRequest in a macro function unless the function is called from a subroutine.

Thursday, November 10, 2011

Formatting code for HTML display

Hi,

This is really a great link
http://www.manoli.net/csharpformat/


to format c# or VB.NET/VBA/VB code in HTML 4 format to display on the web

How to Create a task or appointment using VBA Code in outlook 2010

Hi,
I have changed the original code at this link to show you how to create either a task or an appointment from an e-mail item.
This is very handy when you want quickly create task/appointment without wasting your time to attach the original mail.
The only problem is that the item is attached at the end of the file. This is due to a bug in Outlook 2008/2010 for which the postion item does not work.
Just copy and paste the code below into an outlook module to make it work.




Sub CreateTaskFromMail()
Const mailItem_c As String = "MailItem"
Dim OE As Outlook.Explorer
Dim MI As Outlook.MailItem
Dim AI As Outlook.AppointmentItem
Dim TI As Outlook.TaskItem

Set OE = Application.ActiveExplorer

'Abort sub if no item selected:
If OE.Selection.Count < 1 Then
MsgBox "Please select an already saved message before" & vbCrLf & _
"attempting to create a task" & vbCrLf & _
"with this button ...", vbInformation, "No message selected ..."
Exit Sub
'Abort sub if item selected is not a MailItem.
ElseIf TypeName(OE.Selection(1)) <> mailItem_c Then
MsgBox "You must select a mail item...", vbInformation, "Invalid selection..."
Exit Sub
End If

Set MI = OE.Selection(1)
Set TI = Application.CreateItem(olTaskItem)
With TI
.Subject = MI.Subject
.Body = .Body & vbCrLf & vbCrLf
.Body = .Body & "-----Original Message-----" & vbCrLf
.Body = .Body & "From: " & MI.Sender & " [mailto:" & MI.SenderEmailAddress & "]" & vbCrLf
.Body = .Body & "Sent: " & Format(MI.SentOn, "DD MMMM YYYY HH:MM:SS") & vbCrLf
.Body = .Body & "To: " & MI.To & vbCrLf
.Body = .Body & "Cc: " & MI.CC & vbCrLf
.Body = .Body & "Subject: " & MI.Subject & vbCrLf
.Body = .Body & vbCrLf
.Body = .Body & MI.Body
'.StartDate = Date
'.DueDate = Date + 1
'.ReminderTime = .DueDate & " 10:00"


Select Case MsgBox("Do you want to attach the original mail?" & vbLf, _
vbYesNoCancel + vbQuestion, "Add Mail as Attachment ...")
Case vbYes
TI.Body = "View Original Mail attacched at the bottom" & vbCrLf & TI.Body
TI.Attachments.Add MI, , 1 'Position does not work. It is a bug in Outlook 2008/2010
TI.Display
Case vbNo
TI.Display
Case vbCancel
Exit Sub
End Select



End With

End Sub



Sub CreateAppointmentFromMail()
Const mailItem_c As String = "MailItem"
Dim OE As Outlook.Explorer
Dim MI As Outlook.MailItem
Dim AI As Outlook.AppointmentItem
Dim TI As Outlook.TaskItem

Set OE = Application.ActiveExplorer

'Abort sub if no item selected:
If OE.Selection.Count < 1 Then
MsgBox "Please select an already saved message before" & vbCrLf & _
"attempting to create an appointment" & vbCrLf & _
"with this button ...", vbInformation, "No message selected ..."
Exit Sub
'Abort sub if item selected is not a MailItem.
ElseIf TypeName(OE.Selection(1)) <> mailItem_c Then
MsgBox "You must select a mail item...", vbInformation, "Invalid selection..."
Exit Sub
End If

Set MI = OE.Selection(1)
Set AI = Outlook.CreateItem(olAppointmentItem)
With AI
.Subject = MI.Subject
.Body = .Body & vbCrLf & vbCrLf
.Body = .Body & "-----Original Message-----" & vbCrLf
.Body = .Body & "From: " & MI.Sender & " [mailto:" & MI.SenderEmailAddress & "]" & vbCrLf
.Body = .Body & "Sent: " & Format(MI.SentOn, "DD MMMM YYYY HH:MM:SS") & vbCrLf
.Body = .Body & "To: " & MI.To & vbCrLf
.Body = .Body & "Cc: " & MI.CC & vbCrLf
.Body = .Body & "Subject: " & MI.Subject & vbCrLf
.Body = .Body & vbCrLf
.Body = .Body & MI.Body
'.StartDate = Date
'.DueDate = Date + 1
'.ReminderTime = .DueDate & " 10:00"


Select Case MsgBox("Do you want to attach the original mail?" & vbLf, _
vbYesNoCancel + vbQuestion, "Add Mail as Attachment ...")
Case vbYes
AI.Body = "View Original Mail attacched at the bottom" & vbCrLf & AI.Body
AI.Attachments.Add MI, , 1 'Position does not work. It is a bug in Outlook 2008/2010
AI.Display
Case vbNo
AI.Display
Case vbCancel
Exit Sub
End Select



End With

End Sub




Sub NewTaskOrAppoitmentFromMail()
Const mailItem_c As String = "MailItem"
Dim OE As Outlook.Explorer
Dim MI As Outlook.MailItem
Dim AI As Outlook.AppointmentItem
Dim TI As Outlook.TaskItem

Set OE = Application.ActiveExplorer

'Abort sub if no item selected:
If OE.Selection.Count < 1 Then
MsgBox "Please select an already saved message before" & vbCrLf & _
"attempting to create an appointment or task" & vbCrLf & _
"with this button ...", vbInformation, "No message selected ..."
Exit Sub
'Abort sub if item selected is not a MailItem.
ElseIf TypeName(OE.Selection(1)) <> mailItem_c Then
MsgBox "You must select a mail item...", vbInformation, "Invalid selection..."
Exit Sub
End If

Set MI = OE.Selection(1)
'Beep
Select Case MsgBox("Do you want to create a Task?" & vbLf & _
"To Add Task (Yes) / To Add Appointment (No) / To Quit (Cancel)" & _
vbCrLf, vbYesNoCancel + vbQuestion, "Create a task or appointment ...")
Case vbNo 'If No, create appointment
Set AI = Outlook.CreateItem(olAppointmentItem)
With AI
.Subject = MI.Subject
.Body = "View Original Mail attacched at the bottom"
.Body = .Body & vbCrLf & vbCrLf
.Body = .Body & "-----Original Message-----" & vbCrLf
.Body = .Body & "From: " & MI.Sender & " [mailto:" & MI.SenderEmailAddress & "]" & vbCrLf
.Body = .Body & "Sent: " & Format(MI.SentOn, "DD MMMM YYYY HH:MM:SS") & vbCrLf
.Body = .Body & "To: " & MI.To & vbCrLf
.Body = .Body & "Cc: " & MI.CC & vbCrLf
.Body = .Body & "Subject: " & MI.Subject & vbCrLf
.Body = .Body & vbCrLf
.Body = .Body & MI.Body
.Attachments.Add MI, , 1
.Display
End With
Case vbYes
'If Yes, create task with no due or start date
Set TI = Application.CreateItem(olTaskItem)
With TI
.Subject = MI.Subject
.Body = "View Original Mail attacched at the bottom"
.Body = .Body & vbCrLf & vbCrLf
.Body = .Body & "-----Original Message-----" & vbCrLf
.Body = .Body & "From: " & MI.Sender & " [mailto:" & MI.SenderEmailAddress & "]" & vbCrLf
.Body = .Body & "Sent: " & Format(MI.SentOn, "DD MMMM YYYY HH:MM:SS") & vbCrLf
.Body = .Body & "To: " & MI.To & vbCrLf
.Body = .Body & "Cc: " & MI.CC & vbCrLf
.Body = .Body & "Subject: " & MI.Subject & vbCrLf
.Body = .Body & vbCrLf
.Body = .Body & MI.Body
.Attachments.Add MI, , 1

'.StartDate = Date
'.DueDate = Date + 1
'.ReminderTime = .DueDate & " 10:00"
'.Save
.Display



End With
'Case vbCancel
' Exit Sub
End Select
End Sub

Thursday, September 9, 2010

MS scripting run time and MS Regular Expression in VB 6.0 or VBA

Hi,
Two nice library that help you speed up your VB 6.0 / VBA development time are the
1) Microsoft Scripting Run Time library

   The two most interestinjg objects here are the
   Dictionary object which is an hashtable. Its only major draw back  is that it does not expose the   NewEnum function, which means that while you can wrap it up to create a strongly type dictionary object, you cannot create a NewEnum method to loop through the collection using the For Each statement

   FileSystemObject which let you easily access to files and folders

2) Microsoft VBScript Regural Expression 5.5.

    This is a really nice library to use to test a string using regular expression.
    This is an example on how it works:


How to access .Net library from VBA and VB 6.0

Download Example from Microsoft  here

Here you can find a couple of links that will explain you how to access the .Net Framework Class Libray (FCL) from VB6.0 and VBA.

This link contains a series of articles on COM and .NET
Link 1

This one shows you to use some .NET library withoug writing any line of code Link 2
In a netshell

  1. Download and install either version 1.1 or version 2.0 of the .NET Framework. If you have installed Visual Studio .NET 2003 or Visual Studio 2005, or any of the Express products, then the .NET framework is already installed.
  2. Execute Register.bat, which is included in the code download for this article. This registers the .NET framework System.dll so that it can be called as a COM object.
  3. Start Visual Basic 6.
  4. In the New Project dialog, select Standard EXE, and click OK.
  5. Add a CommandButton and Image control to the form.
  6. Set the Stretch property of the Image to true.
  7. Select the Project | References menu command.
  8. Click Browse.
  9. For v1.1 of the .NET Framework, select C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\system.tlb. For v2.0 of the .NET framework, select C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\system.tlb.
  10. Click OK
 Thies the the contens of the Register.bat file you can find on the link above.

path=%path%;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727;C:\Program Files\Microsoft.NET\SDK\v1.1\Bin;C:\Program Files\Microsoft.NET\SDK\v1.1\Bin;C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322;
regasm "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\system.dll"
regasm "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\system.dll"
pause
  
as you can see it use the regasm utilty to register as a COM object the system.dll file.

More interestingly:

The FCL also ships with a number of powerful collection classes, which include:
  • ArrayList—An array class that doesn't have a fixed size. You can just keep adding items to it.
  • Hashtable—This class is similar to the Scripting.Dictionary class. You can add items and look them up by key.
  • Queue—This is a first in, first out (FIFO) collection. You push items in, and then read them out at a later time in the same order.
  • Stack—A first-in, last out (FILO) collection. You push items onto the stack, and then pop them off in reverse order.
  • SortedList—Similar to the Hashtable, except when you iterate through the items, they're always sorted by the key.
 To use this classe just add

a reference to either C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.tlb or C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.tlb, depending on the version of the framework that you want to use. Mscorlib is part of the Microsoft .NET Framework, and contains the collection classes.

and you are ready to go.

The are few caveats though
1) Intellisense will not work!!!.  To sort this out you need to write your own COM wrapper (I will tell you how to do it)
2) To use the For Each loop to run through the collection you need to define a variable as IEnumerable and assign the list to this interface. This is because the For Each loop need an explicit GetEnumerable (equal to NewEnum) method to work
   
    Dim objSortedList As mscorlib.SortedList
    Dim Enum As mscorlib.IEnumerable
    Set Enum = objSortedList
3) If you follow step 2 instruction you will have the For Each loop work, however the single object returned for an HashTable or a SortedList is a DictionaryEntry. This means tha VB 6.0 will not be able to access anyway its properties
 
All of these problems can be solved coding your own COM class in C#.

This is explained on this Link3 you can also find some info on my blog here and I will soon post some sample projects

Wednesday, September 8, 2010

How to create custom collection in VBA tricks

This post has been update here


You can find attached here the code that shows you how to create a strongly typed collection in VBA that has both a default Item property and that can be iterated with the For Each Loop.

As you will see from this blog post, some vba attributes are needed to be assigned to specific Collection properties, however these are not visible in the VBA IDE, but you can see them using notepad.




This solution was taken from a forum entry I found on the web
In VBA You can create both a defaul property and a default enumerator, but the process is a bit more manual.
If you export a .cls file from one of your VB6 proceedures and view it in a Notepad, 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


It is important to note that the Attribute directive are just below the Property Signatures.
If they are not there, 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") and then (5) Manually add the Attribute Lines as shown above, (6) Re-Import the edited Class.

As for one comment in this post another way is
1) Add those attributes in the VBA IDE. You will get  a syntax error. Ignore it
2) Click on the Class and Remove
3) Say Yes when you are asked to Export it
4) Import it again

Pay attention to not delete the class. Otherwise do: Export, Delete, Import.


(Btw, 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...