"Is" operator equivalent for comparing two arrays - vba

The following code doesn't work because the operator "Is" can only be used to compare two object.
Is there an equivalent of "Is" for comparing two arrays? and I mean the pointers of the arrays, not (only) their content.
Example of use:
sub testMAIN()
dim arrFirst(1 to 3)
testSUB arrFirst
dim arrSecond(1 to 3)
testSUB arrSecond
end sub
sub testSUB(arr)
if arr Is arrFirst then msgbox "is the first"
end sub
I think I am searching for a shorter form of: "if VarPtr(lBound(arr)) = VarPtr(lBound(arrFirst)) then [...]"

Related

Excel Vba-Similar Function

I'm so confused in resolving this:
In VBA syntax there is this function:
If Left("abcdef",3)="abc" then
Msgbox "True"
This function is too simple but is there any way for e.g.
To have something like this
If left("abcdef",3) is in ["A".."Z"] or if left("1265avd0",2) is in [1..9]
Which mean checking if left ("abcdef",3) which is equal to "abc" is in this Interval ["A".."Z"] or checking if the left("123avd0",2) is numeric and is in the interval of [1..9]
Hope you are understading what I want to do
Can anyone Light me in doing This?
You can use the Like operator for this simple type of comparison.
Option Explicit
Option Compare Text 'for case insensitive matching
Sub dural()
Const S1 As String = "abcdef"
Const S2 As String = "1265avd0"
Debug.Print Left(S1, 3) Like "[A-Z][A-Z][A-Z]"
Debug.Print Left(S2, 2) Like "[1-9][1-9]"
End Sub
Both will return TRUE in this instance.
As pointed out by #chrisneilsen, the comparisons can be simplified to:
S1 Like "[A-Z][A-Z][A-Z]*"
S2 Like "[1-9][1-9]*"
And you can test for either with something like:
If Left(myString, 3) Like "[A-Z][A-Z][A-Z]" or _
Left(myString, 2) Like "[1-9][1-9]" then
'do something
End if
And in the simplified version:
If myString Like "[A-Z][A-Z][A-Z]*" or _
myString Like "[1-9][1-9]*" then
'do something
End if
More complex pattern matching can be done using Regular Expressions.
If you prefer to not set Option Compare then change the alpha patterns to "[A-Za-z]"
You can use Regular Expressions ("RegEx") for this.
The pattern has a non-capturing group (?:...) that can determine if the start of the string ^ contains either three letters of any case: [a-zA-Z]{3} or | two digits \d{2}.
If either criterion is met, then the MsgBox will return True, otherwise, it returns False.
Sub test()
Dim RegEx As New RegExp, testString As String
testString = "53bcko390872"
With RegEx
.Pattern = "^(?:[a-zA-Z]{3}|\d{2})"
MsgBox .test(testString) 'will prompt True/False
End With
End Sub
You can replace the MsgBox line with something useful, such as
If .test(testString) [= True] Then...
Click Here for a live demo on the regex working (optional).
Note that you will need to set a reference to Microsoft VBScript Regular Expressions x.x

VBA Multi-Dimensional Arrays - Array Literal Syntax

I want to create a multi-dimensional array where I assign all the values at once instead of going through all the array coordinate values one by one. I believe this is called setting 'array literals'. Anyway, all my variables are string values. The code below doesn't give me a syntax error but when I step through I'm getting a "Compile error: Can't assign to array" message on pkg= line. How do I make this work?
Sub test_array2()
Dim pkg(2, 2) As String
pkg = [{"PRetail","Retail Packaged"};{"PFoodservice","Foodservice
Packaged"}]
Debug.Print pkg(1, 1)
End Sub
You cannot assign directly to an array like that, so you need to use a variant:
Sub test_array2()
Dim pkg As Variant
pkg = [{"PRetail","Retail Packaged";"PFoodservice","FoodservicePackaged "}]
Debug.Print pkg(1, 1)
End Sub

VBA: Non exclusive ENUM or Ignore Type 13 error?

I want a list of possible values to show up when I call a function func1. I found a way to achieve this in VBA via ENUM, but that doesn't work in a worksheet. So I've created a function e that will convert certain strings into the right enum value. Unfortunately, I can't find a way to call this function func1 with either a string or the enum value without getting an error or losing functionality. Here's what I've got so far:
Enum eLanguages
evEnglish = 2
evItalian = 3
'and so on
End Enum
Function e(vString)
Select Case vString
Case "english", "eng", "en", "e"
e = evEnglish
Case "italian", "italien", "it", "i"
e = evItalian
Case Else
e = vString '(= will keep the value if it's already a number)
End Select
End Function
Option 1: Shows the list of possible values when typing in VBA, but won't work for string inputs
Function func1(var As eLanguages)
func1 = e(var)
End Function
Sub test1()
MsgBox func1(evEnglish) 'Will result in 2 (through the enum and the e function)
MsgBox func1("e") 'Type 13 error
End Sub
Option 2: Gives the right result but I don't get the list of possible values within VBA
Function func2(var)
func2 = e(var)
End Function
Sub test2()
MsgBox func2(evEnglish) 'Will result in 2 (through the enum and the e function)
MsgBox func2("e") 'Will result in 2 (through the e function)
End Sub
Note: Calling either of the two functions like this will give the right result, but obviously I'm not keen on having to write e() everytime I call the function:
MsgBox func1(e("e"))
So do you have any suggestions on how to ignore the Type 13 error or how to include an "any string allowed" option in the ENUM declaration?
Thanks in advance.
#CinyMeister 's idea is interesting. A slight variation -- make both arguments optional and use if - then - else, together with the IsMissing function to merge the two arguments back into one: Function func3(Optional x As eLanguages, Optional y As String)
Dim var As Variant
If IsMissing(x) Then
var = y
Else
var = x
End If
func3 = e(var)
End Function
Sub test()
MsgBox func3(evEnglish)
MsgBox func3(, "it")
End Sub
Even with a large number of parameters, this sort of thing will work -- when you invoke the function in the VBA editor you can tab through the options by repeatedly pressing the comma key. Parameters that are given by enumerations will evoke intellisense drop-downs. Other parameters will hopefully have descriptive names.
I just had another idea, but I don't think it would work as a superelegant solution either.
I could have 2 functions, one for Excel, one for VBA. Like that:
Function funcV(var As eLanguages)
funcV = SOMETHING
End Function
Function funcE(var)
funcE= funcV(e(var)) '(which is "SOMETHING")
End Function
The question is whether I would remember which of my many functions have a sister function. There's no way to capture the Type 13 error as an "on error" event and redirect automatically, is there?

Checking if a value is a member of a list

I have to check a piece of user input against a list of items; if the input is in the list of items, then direct the flow one way. If not, direct the flow to another.
This list is NOT visible on the worksheet itself; it has to be obfuscated under code.
I have thought of two strategies to do this:
Declare as an enum and check if input is part of this enum, although I'm not sure on the syntax for this - do I need to initialise the enum every time I want to use it?
Declare as an array and check if input is part of this array.
I was wondering for VBA which is better in terms of efficiency and readability?
You can run a simple array test as below where you add the words to a single list:
Sub Main1()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test("dog") 'True
Debug.Print "horse", Test("horse") 'False
End Sub
Function Test(strIn As String) As Boolean
Test = Not (IsError(Application.Match(strIn, arrList, 0)))
End Function
Or if you wanted to do a more detailed search and return a list of sub-string matches for further work then use Filter. This code would return the following via vFilter if looking up dog
dog, dogfish
In this particular case the code then checks for an exact match for dog.
Sub Main2()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test1("dog")
Debug.Print "horse", Test1("horse")
End Sub
Function Test1(strIn As String) As Boolean
Dim vFilter
Dim lngCnt As Long
vFilter = Filter(arrList, strIn, True)
For lngCnt = 0 To UBound(vFilter)
If vFilter(lngCnt) = strIn Then
Test1 = True
Exit For
End If
Next
End Function
Unlike in .NET languages VBA does not expose Enum as text. It strictly is a number and there is no .ToString() method that would expose the name of the Enum. It's possible to create your own ToString() method and return a String representation of an enum. It's also possible to enumerate an Enum type. Although all is achievable I wouldn't recommend doing it this way as things are overcomplicated for such a single task.
How about you create a Dictionary collection of the items and simply use Exist method and some sort of error handling (or simple if/else statements) to check whether whatever user inputs in the input box exists in your list.
For instance:
Sub Main()
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
myList.Add "item1", 1
myList.Add "item2", 2
myList.Add "item3", 3
Dim userInput As String
userInput = InputBox("Type something:")
If myList.Exists(userInput) Then
MsgBox userInput & " exists in the list"
Else
MsgBox userInput & " does not exist in the list"
End If
End Sub
Note: If you add references to Microsoft Scripting Runtime library you then will be able to use the intelli-sense with the myList object as it would have been early bound replacing
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
with
Dim myList as Dictionary
Set myList = new Dictionary
It's up to you which way you want to go about this and what is more convenient. Note that you don't need to add references if you go with the Late Binding while references are required if you want Early Binding with the intelli-sense.
Just for the sake of readers to be able to visualize the version using Enum let me demonstrate how this mechanism could possibly work
Enum EList
item1
item2
item3
[_Min] = item1
[_Max] = item3
End Enum
Function ToString(eItem As EList) As String
Select Case eItem
Case EList.item1
ToString = "item1"
Case EList.item2
ToString = "item2"
Case EList.item3
ToString = "item3"
End Select
End Function
Function Exists(userInput As String) As Boolean
Dim i As EList
For i = EList.[_Min] To EList.[_Max]
If userInput = ToString(i) Then
Exists = True
Exit Function
End If
Next
Exists = False
End Function
Sub Main()
Dim userInput As String
userInput = InputBox("type something:")
MsgBox Exists(userInput)
End Sub
First you declare your List as Enum. I have added only 3 items for the example to be as simple as possible. [_Min] and [_Max] indicate the minimum value and maximum value of enum (it's possible to tweak this but again, let's keep it simple for now). You declare them both to be able to iterate over your EList.
ToString() method returns a String representation of Enum. Any VBA developer realizes at some point that it's too bad VBA is missing this as a built in feature. Anyway, you've got your own implementation now.
Exists takes whatever userInput stores and while iterating over the Enum EList matches against a String representation of your Enum. It's an overkill because you need to call many methods and loop over the enum to be able to achieve what a simple Dictionary's Exists method does in one go. This is mainly why I wouldn't recommend using Enums for your specific problem.
Then in the end you have the Main sub which simply gathers the input from the user and calls the Exists method. It shows a Message Box with either true or false which indicates if the String exists as an Enum type.
Just use the Select Case with a list:
Select Case entry
Case item1,item2, ite3,item4 ' add up to limit for Case, add more Case if limit exceeded
do stuff for being in the list
Case Else
do stuff for not being in list
End Select

Hidden features of VBA

Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Which features of the VBA language are either poorly documented, or simply not often used?
This trick only works in Access VBA, Excel and others won't allow it. But you can make a Standard Module hidden from the object browser by prefixing the Module name with an underscore. The module will then only be visible if you change the object browser to show hidden objects.
This trick works with Enums in all vb6 based version of VBA. You can create a hidden member of an Enum by encasing it's name in brackets, then prefixing it with an underscore. Example:
Public Enum MyEnum
meDefault = 0
meThing1 = 1
meThing2 = 2
meThing3 = 3
[_Min] = meDefault
[_Max] = meThing3
End Enum
Public Function IsValidOption(ByVal myOption As MyEnum) As Boolean
If myOption >= MyEnum.[_Min] Then IsValidOption myOption <= MyEnum.[_Max]
End Function
In Excel-VBA you can reference cells by enclosing them in brackets, the brackets also function as an evaluate command allowing you to evaluate formula syntax:
Public Sub Example()
[A1] = "Foo"
MsgBox [VLOOKUP(A1,A1,1,0)]
End Sub
Also you can pass around raw data without using MemCopy (RtlMoveMemory) by combining LSet with User Defined Types of the same size:
Public Sub Example()
Dim b() As Byte
b = LongToByteArray(8675309)
MsgBox b(1)
End Sub
Private Function LongToByteArray(ByVal value As Long) As Byte()
Dim tl As TypedLong
Dim bl As ByteLong
tl.value = value
LSet bl = tl
LongToByteArray = bl.value
End Function
Octal & Hex Literals are actually unsigned types, these will both output -32768:
Public Sub Example()
Debug.Print &H8000
Debug.Print &O100000
End Sub
As mentioned, passing a variable inside parenthesis causes it to be passed ByVal:
Sub PredictTheOutput()
Dim i&, j&, k&
i = 10: j = i: k = i
MySub (i)
MySub j
MySub k + 20
MsgBox Join(Array(i, j, k), vbNewLine), vbQuestion, "Did You Get It Right?"
End Sub
Public Sub MySub(ByRef foo As Long)
foo = 5
End Sub
You can assign a string directly into a byte array and vice-versa:
Public Sub Example()
Dim myString As String
Dim myBytArr() As Byte
myBytArr = "I am a string."
myString = myBytArr
MsgBox myString
End Sub
"Mid" is also an operator. Using it you overwrite specific portions of strings without VBA's notoriously slow string concatenation:
Public Sub Example1()
''// This takes about 47% of time Example2 does:
Dim myString As String
myString = "I liek pie."
Mid(myString, 5, 2) = "ke"
Mid(myString, 11, 1) = "!"
MsgBox myString
End Sub
Public Sub Example2()
Dim myString As String
myString = "I liek pie."
myString = "I li" & "ke" & " pie" & "!"
MsgBox myString
End Sub
There is an important but almost always missed feature of the Mid() statement. That is where Mid() appears on the left hand side of an assignment as opposed to the Mid() function that appears in the right hand side or in an expression.
The rule is that if the if the target string is not a string literal, and this is the only reference to the target string, and the length of segment being inserted matches the length of the segment being replaced, then the string will be treated as mutable for the operation.
What does that mean? It means that if your building up a large report or a huge list of strings into a single string value, then exploiting this will make your string processing much faster.
Here is a simple class that benefits from this. It gives your VBA the same StringBuilder capability that .Net has.
' Class: StringBuilder
Option Explicit
Private Const initialLength As Long = 32
Private totalLength As Long ' Length of the buffer
Private curLength As Long ' Length of the string value within the buffer
Private buffer As String ' The buffer
Private Sub Class_Initialize()
' We set the buffer up to it's initial size and the string value ""
totalLength = initialLength
buffer = Space(totalLength)
curLength = 0
End Sub
Public Sub Append(Text As String)
Dim incLen As Long ' The length that the value will be increased by
Dim newLen As Long ' The length of the value after being appended
incLen = Len(Text)
newLen = curLength + incLen
' Will the new value fit in the remaining free space within the current buffer
If newLen <= totalLength Then
' Buffer has room so just insert the new value
Mid(buffer, curLength + 1, incLen) = Text
Else
' Buffer does not have enough room so
' first calculate the new buffer size by doubling until its big enough
' then build the new buffer
While totalLength < newLen
totalLength = totalLength + totalLength
Wend
buffer = Left(buffer, curLength) & Text & Space(totalLength - newLen)
End If
curLength = newLen
End Sub
Public Property Get Length() As Integer
Length = curLength
End Property
Public Property Get Text() As String
Text = Left(buffer, curLength)
End Property
Public Sub Clear()
totalLength = initialLength
buffer = Space(totalLength)
curLength = 0
End Sub
And here is an example on how to use it:
Dim i As Long
Dim sb As StringBuilder
Dim result As String
Set sb = New StringBuilder
For i = 1 to 100000
sb.Append CStr( i)
Next i
result = sb.Text
VBA itself seems to be a hidden feature. Folks I know who've used Office products for years have no idea it's even a part of the suite.
I've posted this on multiple questions here, but the Object Browser is my secret weapon. If I need to ninja code something real quick, but am not familiar with the dll's, Object Browser saves my life. It makes it much easier to learn the class structures than MSDN.
The Locals Window is great for debugging as well. Put a pause in your code and it will show you all the variables, their names, and their current values and types within the current namespace.
And who could forget our good friend Immediate Window? Not only is it great for Debug.Print standard output, but you can enter in commands into it as well. Need to know what VariableX is?
?VariableX
Need to know what color that cell is?
?Application.ActiveCell.Interior.Color
In fact all those windows are great tools to be productive with VBA.
It's not a feature, but a thing I have seen wrong so many times in VBA (and VB6): Parenthesis added on method calls where it will change semantics:
Sub Foo()
Dim str As String
str = "Hello"
Bar (str)
Debug.Print str 'prints "Hello" because str is evaluated and a copy is passed
Bar str 'or Call Bar(str)
Debug.Print str 'prints "Hello World"
End Sub
Sub Bar(ByRef param As String)
param = param + " World"
End Sub
Hidden Features
Although it is "Basic", you can use OOP - classes and objects
You can make API calls
Possibly the least documented features in VBA are those you can only expose by selecting "Show Hidden Members" on the VBA Object Browser. Hidden members are those functions that are in VBA, but are unsupported. You can use them, but microsoft might eliminate them at any time. None of them has any documentation provided, but you can find some on the web. Possibly the most talked about of these hidden features provides access to pointers in VBA. For a decent writeup, check out; Not So Lightweight - Shlwapi.dll
Documented, but perhaps more obscure (in excel anyways) is using ExecuteExcel4Macro to access a hidden global namespace that belongs to the entire Excel application instance as opposed to a specific workbook.
You can implement interfaces with the Implements keyword.
Dictionaries. VBA is practically worthless without them!
Reference the Microsoft Scripting Runtime, use Scripting.Dictionary for any sufficiently complicated task, and live happily ever after.
The Scripting Runtime also gives you the FileSystemObject, which also comes highly recommended.
Start here, then dig around a bit...
http://msdn.microsoft.com/en-us/library/aa164509%28office.10%29.aspx
Typing VBA. will bring up an intellisense listing of all the built-in functions and constants.
With a little work, you can iterate over custom collections like this:
' Write some text in Word first.'
Sub test()
Dim c As New clsMyCollection
c.AddItems ActiveDocument.Characters(1), _
ActiveDocument.Characters(2), _
ActiveDocument.Characters(3), _
ActiveDocument.Characters(4)
Dim el As Range
For Each el In c
Debug.Print el.Text
Next
Set c = Nothing
End Sub
Your custom collection code (in a class called clsMyCollection):
Option Explicit
Dim m_myCollection As Collection
Public Property Get NewEnum() As IUnknown
' This property allows you to enumerate
' this collection with the For...Each syntax
' Put the following line in the exported module
' file (.cls)!'
'Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = m_myCollection.[_NewEnum]
End Property
Public Sub AddItems(ParamArray items() As Variant)
Dim i As Variant
On Error Resume Next
For Each i In items
m_myCollection.Add i
Next
On Error GoTo 0
End Sub
Private Sub Class_Initialize()
Set m_myCollection = New Collection
End Sub
Save 4 whole keystrokes by typing debug.? xxx instead of debug.print xxx.
Crash it by adding: enum foo: me=0: end enum to the top of a module containing any other code.
Support for localized versions, which (at least in the previous century) supported expressions using localized values. Like Pravda for True and FaƂszywy (not too sure, but at least it did have the funny L) for False in Polish... Actually the English version would be able to read macros in any language, and convert on the fly. Other localized versions would not handle that though.
FAIL.
The VBE (Visual Basic Extensibility) object model is a lesser known and/or under-utilized feature. It lets you write VBA code to manipulate VBA code, modules and projects. I once wrote an Excel project that would assemble other Excel projects from a group of module files.
The object model also works from VBScript and HTAs. I wrote an HTA at one time to help me keep track of a large number of Word, Excel and Access projects. Many of the projects would use common code modules, and it was easy for modules to "grow" in one system and then need to be migrated to other systems. My HTA would allow me to export all modules in a project, compare them to versions in a common folder and merge updated routines (using BeyondCompare), then reimport the updated modules.
The VBE object model works slightly differently between Word, Excel and Access, and unfortunately doesn't work with Outlook at all, but still provides a great capability for managing code.
IsDate("13.50") returns True but IsDate("12.25.2010") returns False
This is because IsDate could be more precisely named IsDateTime. And because the period (.) is treated as a time separator and not a date separator. See here for a full explanation.
VBA supports bitwise operators for comparing the binary digits (bits) of two values. For example, the expression 4 And 7 evaluates the bit values of 4 (0100) and 7 (0111) and returns 4 (the bit that is on in both numbers.) Similarly the expression 4 Or 8 evaluates the bit values in 4 (0100) and 8 (1000) and returns 12 (1100), i.e. the bits where either one is true.
Unfortunately, the bitwise operators have the same names at the logical comparison operators: And, Eqv, Imp, Not, Or, and Xor. This can lead to ambiguities, and even contradictory results.
As an example, open the Immediate Window (Ctrl+G) and enter:
? (2 And 4)
This returns zero, since there are no bits in common between 2 (0010) and 4 (0100).
Deftype Statements
This feature exists presumably for backwards-compatibility. Or to write hopelessly obfuscated spaghetti code. Your pick.