When is it appropriate to use a class in Visual Basic for Applications (VBA)?
I'm assuming the accelerated development and reduction of introducing bugs is a common benefit for most languages that support OOP. But with VBA, is there a specific criterion?
It depends on who's going to develop and maintain the code. Typical "Power User" macro writers hacking small ad-hoc apps may well be confused by using classes. But for serious development, the reasons to use classes are the same as in other languages. You have the same restrictions as VB6 - no inheritance - but you can have polymorphism by using interfaces.
A good use of classes is to represent entities, and collections of entities. For example, I often see VBA code that copies an Excel range into a two-dimensional array, then manipulates the two dimensional array with code like:
Total = 0
For i = 0 To NumRows-1
Total = Total + (OrderArray(i,1) * OrderArray(i,3))
Next i
It's more readable to copy the range into a collection of objects with appropriately-named properties, something like:
Total = 0
For Each objOrder in colOrders
Total = Total + objOrder.Quantity * objOrder.Price
Next i
Another example is to use classes to implement the RAII design pattern (google for it). For example, one thing I may need to do is to unprotect a worksheet, do some manipulations, then protect it again. Using a class ensures that the worksheet will always be protected again even if an error occurs in your code:
--- WorksheetProtector class module ---
Private m_objWorksheet As Worksheet
Private m_sPassword As String
Public Sub Unprotect(Worksheet As Worksheet, Password As String)
' Nothing to do if we didn't define a password for the worksheet
If Len(Password) = 0 Then Exit Sub
' If the worksheet is already unprotected, nothing to do
If Not Worksheet.ProtectContents Then Exit Sub
' Unprotect the worksheet
Worksheet.Unprotect Password
' Remember the worksheet and password so we can protect again
Set m_objWorksheet = Worksheet
m_sPassword = Password
End Sub
Public Sub Protect()
' Protects the worksheet with the same password used to unprotect it
If m_objWorksheet Is Nothing Then Exit Sub
If Len(m_sPassword) = 0 Then Exit Sub
' If the worksheet is already protected, nothing to do
If m_objWorksheet.ProtectContents Then Exit Sub
m_objWorksheet.Protect m_sPassword
Set m_objWorksheet = Nothing
m_sPassword = ""
End Sub
Private Sub Class_Terminate()
' Reprotect the worksheet when this object goes out of scope
On Error Resume Next
Protect
End Sub
You can then use this to simplify your code:
Public Sub DoSomething()
Dim objWorksheetProtector as WorksheetProtector
Set objWorksheetProtector = New WorksheetProtector
objWorksheetProtector.Unprotect myWorksheet, myPassword
... manipulate myWorksheet - may raise an error
End Sub
When this Sub exits, objWorksheetProtector goes out of scope, and the worksheet is protected again.
I think the criteria is the same as other languages
If you need to tie together several pieces of data and some methods and also specifically handle what happens when the object is created/terminated, classes are ideal
say if you have a few procedures which fire when you open a form and one of them is taking a long time, you might decide you want to time each stage......
You could create a stopwatch class with methods for the obvious functions for starting and stopping, you could then add a function to retrieve the time so far and report it in a text file, using an argument representing the name of the process being timed. You could write logic to log only the slowest performances for investigation.
You could then add a progress bar object with methods to open and close it and to display the names of the current action, along with times in ms and probable time remaining based on previous stored reports etc
Another example might be if you dont like Access's user group rubbish, you can create your own User class with methods for loging in and out and features for group-level user access control/auditing/logging certain actions/tracking errors etc
Of course you could do this using a set of unrelated methods and lots of variable passing, but to have it all encapsulated in a class just seems better to me.
You do sooner or later come near to the limits of VBA, but its quite a powerful language and if your company ties you to it you can actually get some good, complex solutions out of it.
Classes are extremely useful when dealing with the more complex API functions, and particularly when they require a data structure.
For example, the GetOpenFileName() and GetSaveFileName() functions take an OPENFILENAME stucture with many members. you might not need to take advantage of all of them but they are there and should be initialized.
I like to wrap the structure (UDT) and the API function declarations into a CfileDialog class. The Class_Initialize event sets up the default values of the structure's members, so that when I use the class, I only need to set the members I want to change (through Property procedures). Flag constants are implemented as an Enum. So, for example, to choose a spreadsheet to open, my code might look like this:
Dim strFileName As String
Dim dlgXLS As New CFileDialog
With dlgXLS
.Title = "Choose a Spreadsheet"
.Filter = "Excel (*.xls)|*.xls|All Files (*.*)|*.*"
.Flags = ofnFileMustExist OR ofnExplorer
If OpenFileDialog() Then
strFileName = .FileName
End If
End With
Set dlgXLS = Nothing
The class sets the default directory to My Documents, though if I wanted to I could change it with the InitDir property.
This is just one example of how a class can be hugely beneficial in a VBA application.
I use classes if I want to create an self-encapsulated package of code that I will use across many VBA projects that come across for various clients.
I wouldn't say there's a specific criterion, but I've never really found a useful place to use Classes in VBA code. In my mind it's so tied to the existing models around the Office apps that adding additional abstraction outside of that object model just confuses things.
That's not to say one couldn't find a useful place for a class in VBA, or do perfectly useful things using a class, just that I've never found them useful in that environment.
For data recursion (a.k.a. BOM handling), a custom class is critically helpful and I think sometimes indispensable. You can make a recursive function without a class module, but a lot of data issues can't be addressed effectively.
(I don't know why people aren't out peddling BOM library-sets for VBA. Maybe the XML tools have made a difference.)
Multiple form instances is the common application of a class (many automation problems are otherwise unsolvable), I assume the question is about custom classes.
I use classes when I need to do something and a class will do it best:) For instance if you need to respond to (or intercept) events, then you need a class. Some people hate UDTs (user defined types) but I like them, so I use them if I want plain-english self-documenting code. Pharmacy.NCPDP being a lot easier to read then strPhrmNum :) But a UDT is limited, so say I want to be able to set Pharmacy.NCPDP and have all the other properties populate. And I also want make it so you can't accidentally alter the data. Then I need a class, because you don't have readonly properties in a UDT, etc.
Another consideration is just simple readability. If you are doing complex data structures, it's often beneficial to know you just need to call Company.Owner.Phone.AreaCode then trying to keep track of where everything is structured. Especially for people who have to maintain that codebase 2 years after you left:)
My own two cents is "Code With Purpose". Don't use a class without a reason. But if you have a reason then do it:)
You can also reuse VBA code without using actual classes. For example, if you have a called, VBACode. You can access any function or sub in any module with the following syntax:
VBCode.mysub(param1, param2)
If you create a reference to a template/doc (as you would a dll), you can reference code from other projects in the same way.
Developing software, even with Microsoft Access, using Object Oriented Programming is generally a good practice. It will allow for scalability in the future by allowing objects to be loosely coupled, along with a number of advantages. This basically means that the objects in your system will be less dependent on each other, so refactoring becomes a lot easier. You can achieve this is Access using Class Modules. The downside is that you cannot perform Class Inheritance or Polymorphism in VBA. In the end, there's no hard and fast rule about using classes, just best practices. But keep in mind that as your application grows, the easier it is to maintain using classes.
As there is a lot code overhead in using classes in VBA I think a class has to provide more benefit than in other languages:
So this are things to consider before using a class instead of functions:
There is no class-inheritance in vba. So prepare to copy some code when you do similar small things in different classes. This happens especially when you want to work with interfaces and want to implement one interfaces in different classes.
There are no built in constructors in vba-classes. In my case I create a extra function like below to simulate this. But of curse, this is overhead too and can be ignored by the one how uses the class. Plus: As its not possible to use different functions with the same name but different parameters, you have to use different names for your "constructor"-functions. Also the functions lead to an extra debug-step which can be quite annoying.
Public Function MyClass(ByVal someInit As Boolean) As MyClassClass
Set MyClass = New MyClassClass
Call MyClass.Init(someInit)
End Function
The development environment does not provide a "goto definition" for class-names. This can be quite annoying, especially when using classes with interfaces, because you always have to use the module-explorer to jump to the class code.
object-variables are used different to other variable-types in different places. So you have to use a extra "Set" to assign a object
Set varName = new ClassName
if you want to use properties with objects this is done by a different setter. You have to use "set" instead of "let"
If you implement an interface in vba the function-name is named "InterfaceName_functionName" and defined as private. So you can use the interface function only when you cast the Variable to the Interface. If you want to use the function with the original class, you have to create an extra "public" function which only calls the interface function (see below). This creates an extra debug-step too.
'content of class-module: MyClass
implements IMyInterface
private sub IMyInterface_SomeFunction()
'This can only be called if you got an object of type "IMyInterface"
end function
private sub IMyInterface_SomeFunction()
'You need this to call the function when having an object of the type "MyClass"
Call IMyInterface_SomeFunction()
end function
This means:
I !dont! use classes when they would contain no member-variables.
I am aware of the overhead and dont use classes as the default to do things. Usually functions-only is the default way to do things in VBA.
Examples of classes I created which I found to be useful:
Collection-Classes: e.g. StringCollection, LongCollection which provide the collection functionality vba is missing
DbInserter-Class: Class to create insert-statements
Examples of classes I created which I dont found to be useful:
Converter-class: A class which would have provided the functionality for converting variables to other types (e.g. StringToLong, VariantToString)
StringTool-class: A class which would have provided some functionality for strings. e.g. StartsWith
You can define a sql wrapper class in access that is more convenient than the recordsets and querydefs. For example if you want to update a table based on a criteria in another related table, you cannot use joins. You could built a vba recorset and querydef to do that however i find it easier with a class. Also, your application can have some concept that need more that 2 tables, it might be better imo to use classes for that. E.g. You application track incidents. Incident have several attributes that will hold in several tables {users and their contacts or profiles, incident description; status tracking; Checklists to help the support officer to reply tonthe incident; Reply ...} . To keep track of all the queries and relationships involved, oop can be helpful. It is a relief to be able to do Incident.Update(xxx) instead of all the coding ...
In VBA, I prefer classes to modules when:
(frequent case) I want multiple simultaneous instances (objects) of a common structure (class) each with own independent properties.
Example:Dim EdgeTabGoogle as new Selenium.EdgeDriverDim EdgeTabBing as new
Selenium.EdgeDriver'Open both, then do something and read data to and from both, then close both
(sometimes) I want to take advantage of the Class_Initialize and Class_Terminate automatic functions
(sometimes) I want hierarchical tree of procedures (for just variables a chain of "Type" is sufficient), for better readability and Intellisense
(rarely) I want public variables or procedures to not show in Intellisense globally (unless preceded by the object name)
I don't see why the criteria for VBA would be any different from another language, particularly if you are referring to VB.NET.
Related
I learned from internet that our vba macros (subs and functions) should be more or less the size of the screen.
But all my code is written exactly in the opposite: a have a huuuge main sub that calls functions and small subs.
My question is: if I decide to convert my main huge sub into small subs (sub1, sub2, sub3, etc), then what should be the best way to keep the value of a variable that was assigned in sub1 and use it in sub2, that was assigned in sub2 and use it in sub3, and so on? If I have no answer to this question, I am going to save those values in a sheet, but maybe this way is going to cause me trouble that I cannot see by now. Thanks for any help.
A Sub or a Function should have a single and clear purpose, told by its name. If you break down your code in such chunks, you will know what information those methods will need and deliver. At the same time they will become smaller. If you have to choose between size and clarity, go for clarity.
Do NOT put your values in a sheet. That is just a different and less efficient way of using global variables, which is something you should avoid (not at all cost, but almost).
Both methods below would work. In my personal opinion, method 1 keeps code more simple (you avoid to pass big bunches of parameters to each macro) but depending on your need you might choose one or the other.
Global variables
Your code would look something like this:
'Global variables declaration
Dim a As Integer
Dim b As Integer
'
Public Sub mainRoutine()
setA '<-- call first macro
setB '<-- call second macro
MsgBox a + b '<-- show A + B (2 + 3 = 5)
End Sub
'code of sub-routines
Private Sub setA()
a = 2 '<-- since a is global, it will keep the value
End Sub
Private Sub setB()
b = 3 '<-- since b is global, it will keep the value
End Sub
Please note: global means that the value of this variable will live for all execution of your program. Memory will be released once the program ends.
Pass variables as ByRef parameters
Public Sub mainRoutine()
Dim a As Integer '<-- a is local
a = 2 '<-- setting a to 2
setA a '<-- passing a as ByRef parameter
MsgBox a '<-- a is now 3
End Sub
Private Sub setA(ByRef a As Integer)
a = 3 '<-- setting a as 3.
End Sub
Of course, the above method would only allow to keep a = 2/3 during the execution of mainRoutine. But if at some point you execute another macro called by another stack (for example another button in the spreadsheet), you wouldn't be able to access the value of a as you would in method 1.
Important: no, variables on the spreadsheet is not a good idea, unless you don’t need to keep the value after closing and reopening the spreadsheet (in that case you would be using the spreadsheet as a sort of database).
VBA can be considered as an object oriented programming language (OOP).It has 3 of the 4 pillars of OOP:
Abstraction
Encapsulation
Polymorphism
This is discussed in Is VBA an OOP language, and does it support polymorphism?
OOP means understanding the SOLID principles.
Single Responsibility Principle
Open Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Best practice is to adhere to these principles and the first is the Single Responsibility Principle.
This states that your functions/subs should have one responsibility and focus on doing one thing.
Advantages (amongst others):
The reduced code complexity and dependencies mean:
1) Easier to debug as your track down which sub/function the error is raised in
2) Units tests can be more easily be built around code that is based on single responsibility.
3) Can be more easily slotted into a wider code base
To use variables between subs/functions consider:
1) Either public variables, and/or
2) Passing variables as arguments
Other info:
OOP VBA pt.1: Debunking Stuff
Base Reference: Ten Code Conversions for VBA, Visual Basic .NET, and C#
Note: I have already created and imported a *.dll, this question is about aliases.
Let's say the programmatic name of a Test class is TestNameSpace.Test
[ProgId("TestNamespace.Test")]
public class Test ...
Now, say a C# solution has been sealed and compiled into a *.dll and I'm referencing it in a Excel's VBE. Note: at this point I cannot modify the programmatic name as if the *.dll wasn't written by me.
This is in VBA : Instead of declaring a variable like this:
Dim myTest As TestNameSpace.Test
Set myTest = new TestNameSpace.Test
I'd prefer to call it (still in VBE)
Dim myTest As Test
Set myText = new Test
In C# you would normally say
using newNameForTest = TestNamespace.Test;
newNameForTest myTest = new NewNameForTest;
Note: Assume there are no namespace conflicts in the VBA project
Question: is there an equivalent call in VBA to C# using or VB.NET imports aliases?
Interesting question (constantly using them but never thought about their exact meaning). The definition of the Imports statement (same for using) is pretty clear: its only function is shortening the references by removing the corresponding namespaces. Thus, the first question to ask is: has VBA such a thing (namespaces) at all? And the answer is no, as you can read from multiple sources; examples: Link 1 Link 2
In summary, after not having found a single reference to any VBA statement doing something similar to Imports/using and having confirmed that VBA does not consider the "structure" justifying their use (namespaces), I think that I am in a position to say: no, there is not such a thing in VBA.
Additionally you should bear in mind that it wouldn't have any real applicability. For example: when converting a VB.NET code where Imports might be used, like:
Imports Microsoft.Office.Interop.Word
...
Dim wdApp As Application
the code would be changed completely, such that the resulting string will not be so long:
Dim wdApp As Word.Application ' Prefacing the library's display name.
I think that this is a good graphical reason explaining why VBA does not need to have this kind of things: VB.NET accounts for a wide variety of realities which have to be properly classified (namespaces); VBA accounts for a much smaller number of situations and thus can afford to not perform a so systematic, long-named classification.
-------------------------- CLARIFICATION
Imports/using is a mere name shortening, that is, instead of writing whatever.whatever2.whatever3 every time you use an object of the given namespace in a Module/ Class, you add an Imports/using statement at the start which, basically, means: "for all the members of the namespace X, just forget about all the heading bla, bla".
I am not saying that you cannot emulate this kind of behaviour; just highlighting that having an in-built functionality to short names makes sense in VB.NET, where the names can become really long, but not so much in VBA.
The answer is no: there is a built-in VBE feature that recognizes the references added to a project and creates aliases at run-time(VBE's runtime) if there are no name collisions
In case of name conflicts in your registry all . dots will be replaces with _ underscores.
» ProgId's (Programmatic Identifiers)
In COM, it is only used in late-binding. It's how you make a call to create a new object
Dim myObj = CreateObject("TestNamespace.Test")
» EarlyBinding and LateBinding
In early binding you specify the type of object you are creating by using the new keyword. The name of you object should pop up with the VBA's intellisense. It has nothing to do with the ProgId. To retrieve the actual namespace used for your object type - open Object Explorer F2 and locate it there
This article explain where the names come from in Early Binding Section
use the same link for When to use late binding
for MSDN Programmatic Identifiers section please see this
In C/C++, when I have a bunch of functions (pointers), I can store them in an array or a vector and call some of them together in a certain order. Can something similar be done in VBA?
Thanks!
Yes, but I don't recommend it. VBA isn't really built for it. You've tagged this question with Excel, so I will describe how it is done for that Office Product. The general concept applies to most of the Office Suite, but each different product has a different syntax for the Application.Run method.
First, it's important to understand the two different methods of dynamically calling a procedure (sub/function) and when to use each.
Application.Run
Application.Run will either run a subroutine or call a function that is stored in a standard *.bas module.
The first parameter is the name of the procedure (passed in as a string). After that, you can pass up to 30 arguments. (If your procedure requires more than that, refactor for the love of code.)
There are two other important things to note about Application.Run.
You cannot use named arguments. Args must be passed by position.
Objects passed as arguments are converted to values. This means you could experience unexpected issues if you try to run a procedure that requires objects that have default properties as arguments.
Public Sub Test1()
Application.Run "VBAProject.Module1.SomeFunction"
End Sub
The takeaway:
Use Application.Run when you're working with a standard module.
VBA.Interaction.CallByName
CallByName executes a method of an object, or sets/gets a property of an object.
It takes in the instance of the object you want to call the method on as an argument, as well as the method name (again as a string).
Public Sub Test2()
Dim anObj As SomeObject
Dim result As Boolean
result = CallByName(anObj, "IsValid")
End Sub
The takeaway:
Use CallByName when you want to call a method of a class.
No pointers.
As you can see, neither of these methods use actual pointers (at least not externally). They take in strings that they then use to find the pointer to the procedure that you want to execute. So, you'll need to know the exact name of the procedure you want to execute. You'll also need to know which method you need to use. CallByName having the extra burden of requiring an instance of the object you want to invoke. Either way, you can stores these names as strings inside of an array or collection. (Heck, even a dictionary could make sense.)
So, you can either hard code these as strings, or attempt to extract the appropriate procedure names at runtime. In order to extract the procedure names, you'll need to interface with the VBIDE itself via the Microsoft Visual Basic for Applications Extensibility library. Explaining all of that here would require far too much code and effort, but I can point you to some good resources.
Articles & SE Questions:
Chip Pearson's Programming The VBA Editor
Extending the VBA Extensibility Library
Ugly workaround to get the vbext_ProcKind is breaking encapsulation
Automagic testing framework for VBA
How to get the procedure or function name at runtime
Import Lines of Code
Meta Programming in VBA: The VBIDE and Why Documentation is Important
The code from some of my Qs & As:
vbeCodeModule
vbeProcedure
vbeProcedures
A workaround is to enumerate and use a switch statement. You can store enumerated types (longs) in an array. E.g.:
Enum FType
func1
func2
func3
End Enum
Sub CallEnumFunc(f As FType, arg As String)
Select Case f
Case func1: MyFunction1(arg)
Case func2: MyFunction2(arg)
Case func3: MyFunction3(arg)
End Select
End Sub
Dim fArray(1) As FType
fArray(0) = func1
fArray(1) = func2
CallEnumFunc fArray(1), "blah"
this is my first post here.
I like to have some advice on designing a new module for our software.
It's written in vb6 and i want to rewrite it in vb.net. So I want to refactor it.
It does the following:
We got many templates for OpenOffice Documents. (about 300)
Each one has an unique identifier which is in a database and comes as the input.
Dending on this number i want to create the document. e.g. calc or writer or later something else.
Then call a method named doc[template_num] or sth.
I read about invoking methods. I think this as a good way to dynamically call methods by number. Tried to do this. I think I understood now how it works.
But I dont know how to handle the document type.
I want to reuse big parts of code due to all calc documents are created equal at the beginning. But filling the cells is just bit diffrent.
I dont know how to inherit and do method calls with invoke.
Maybe someone has a little code snip for me which can explain this to.
Or maybe some other good idea how to handle this problem.
I am thankful for any new thought on this.
You could, of course, just have a giant Select Case block, like this:
Public Sub CreateDoc(templateNum As Integer)
Select Case templateNum
Case 1
CreateDoc1()
Case 2
CreateDoc2()
Case 3
CreateDoc3()
End Select
End Sub
If you had a fairly small number of case statements that would probably be the better, simpler method. If however, as you described, you have many different possible ID's to look for, then that becomes impractical and I can appreciate your desire to invoke the correct method without a big Select Case block.
However, before I get into that, I feel like I should give you another option. Perhaps you don't really need to have that many different methods. Is it possible, for instance, that half of the template ID's actually all need to be handled in the same way, and the only difference is the template file name that needs to be different. If so, it is conceivable that the Select Case block wouldn't need to be that big. For instance:
Public Sub CreateDoc(templateNum As Integer)
Select Case templateNum
Case 1 To 100
CreateWriterDoc(GetTemplateFilePath(templateNum))
Case 101 To 200
CreateCalcDoc(GetTemplateFilePath(templateNum))
End Select
End Sub
Private Sub CreateWriterDoc(templateFilePath As String)
' Launch Writer with given template file
End Sub
Private Sub CreateCalcDoc(templateFilePath As String)
' Launch Calc with given template file
End Sub
Private Function GetTemplateFilePath(templateNum As Integer) As String
' Retrieve template file path for given ID
' (from DB, Array, List, Dictionary, etc.) and return it
End Sub
To me, that seems like a much simpler solution, if such a thing is possible. If not--if you really have entirely different logic which needs to be executed for each template ID, then there are several ways to do it. The first option would be to create a list of delegates which point to the appropriate method to call for each ID. For instance, you could store them in an array, like this:
Dim createDocDelegates() As Action =
{
AddressOf CreateDoc1,
AddressOf CreateDoc2,
AddressOf CreateDoc3
}
So now, those three delegates are indexed by number (0 through 2) in an array. You can invoke them by number, like this:
createDocDelegates(1).Invoke() ' Calls CreateDoc2
Or, if your ID's aren't sequential, you may want to use a Dictionary(Of Integer, Action), instead, like this:
Dim createDocDelegates As New Dictionary(Of Integer, Action)()
createDocDelegates.Add(1, AddressOf CreateDoc1)
createDocDelegates.Add(7, AddressOf CreateDoc7)
createDocDelegates.Add(20, AddressOf CreateDoc20)
Then you can call one by ID, like this:
createDocDelegates(7).Invoke() ' Calls CreateDoc7
Another option would be to create an Interface for an object that creates a document, like this:
Public Interface IDocCreator
Sub CreateDoc()
End Interface
Then you could implement a separate class for each type of template (each implementing that same interface). For instance:
Public Class Doc1Creator
Implements IDocCreator
Public Sub CreateDoc() Implements IDocCreator
' Do work
End Sub
End Class
Public Class Doc2Creator
Implements IDocCreator
Public Sub CreateDoc() Implements IDocCreator
' Do different work
End Sub
End Class
Then, you can create a list of those objects, like this:
Dim docCreators() As IDocCreator =
{
New DocCreator1(),
New DocCreator2()
}
Or:
Dim docCreators As New Dictionary(Of Integer, IDocCreator)()
docCreators.Add(1, New DocCreator1())
docCreators.Add(7, New DocCreator7())
docCreators.Add(20, New DocCreator7())
Then you can call one like this:
docCreators(1).CreateDoc()
The IDocCreator approach is very similar to the Delegate approach. Neither is the right or wrong way to do it. It depends on your style, what you are comfortable with, and your requirements. The main advantage of the IDocCreator approach is that you could easily add more properties and methods to it in the future. For instance, lets say that you also want to somewhere store a user-friendly, descriptive name for each template. It would be very easy to add a ReadOnly Property Name As String property to the IDocCreator interface, but if you go the list-of-delegates route, that would be more difficult and sloppy.
In any of the above examples, however, you still have to add the complete list of methods or delegates somewhere. So, while it's slightly less ugly than a giant Select Case block, it may still not be enough. If so, the technology you need to use is called Reflection. The System.Reflection namespace contains much of the reflection-related functionality. Reflection allows you to dynamically access and invoke portions of your code. For instance, you can use reflection to get a list of all of the properties or methods that are defined by a given class. Or you can use reflection to get a list of types that are defined by your assembly. Using reflection, it would be possible to get a method, by string name, and then invoke it. So, for instance, if you want to call the "CreateDoc1" method on the current object, you could do it like this:
Me.GetType().GetMethod("CreateDoc1").Invoke(Me, {})
Since you are calling it by its string name, you could build the method name via concatenation:
Dim methodName As String = "CreateDoc" & templateNum.ToString()
Me.GetType().GetMethod(methodName).Invoke(Me, {})
However, if you are going to use the reflection approach, instead of using the name of the method, like that, it would be cleaner, in my opinion, to use a custom attribute to tag each method. For instance, you could create an attribute class which allows you to decorate your methods like this:
<CreateDocMethod(1)>
Public Sub CreateDoc1()
' ...
End Sub
Then, at run-time, you can use reflection to find all of the methods that have that particular attribute and then invoke the right one.
The reason that I saved the discussion on reflection for last is because it is not as efficient and it can lead to brittle code. As such, it's best to only use reflection as a last-resort. There's nothing wrong with reflection, if you really need it, but, as a general rule, if there is another reasonable way to do something, which doesn't require reflection, then you probably ought to be doing it that other way.
As my first language and as completely taught from other's example I never questioned the standard practice in VBA of grouping all variable declarations at the start of the module, routine or function they are scoped to as in this example.
Sub Traditional()
Dim bVariable as Boolean
Dim OtherVariable
' Some code using OtherVariable goes here
'
' Now we use bVariable
bVariable = True
Do While bVariable
bVariable = SomeFunction()
Loop
End Sub
Now I'm learning that standard practice in other languages is to declare variables as close to where they are used as possible, like this:
Sub Defensive()
Dim OtherVariable as String
' Some code using OtherVariable goes here
'
' Now we use bVariable
Dim bVariable as Boolean
bVariable = True
Do While bVariable
bVariable = SomeFunction()
Loop
End Sub
This seems completely sensible to me as a defensive programming practice - in that it limits both span and live time (as explained in Code Complete), so I'm wondering if there is any reason for not doing the same in VBA? Possible reasons I can think of are memory, running time (e.g. repeatedly declaring inside a loop), tradition - arguably a good reason as there must be hundreds of thousands of VBA programmers who expect to see all used variables at the start of the routine. Are there any I've missed that might explain the benefit of this practice or at least where it came from?
I declare all my variables at the top. I think declaring them closer to first use masks (at least) two other problems that should be fixed first.
Procedures too long: If you're procedure is more than fits on a screen, perhaps it's doing too much and should be broken into smaller chunks. You'll also find that unit tests are way easier to write when your procedures are small and only do one thing.
Too many variables: If you have a bunch of related variables, consider using a custom class module or user-defined type. It will make the code more readable and easier to maintain.
If your procedures are short and you're using classes and UDTs, the benefits of declaring the variables at the point of use are lessened or eliminated.
I think both way are just different coding style in VBA
In old C Standard, all Declaration must be on the top, I think many people just adopt this habit and bring it into other PL such as VBA.
Declaring variable on the top is clear for short list of variable names. It will be unreadable for a long list of variable name
Declaring variable close to where it's being used is introduced later. I think this practice has a clear advantage over "declare on the top" for PLs that has optimizer or more scope than VBA. (Like you can declare variables where the scope is visible in a FOR loop only) Then the optimizer will change the scope for you. (In VBA words, it may change a GLOBAL variable to a PROCEDURE variable)
For VBA, no perference