How to find out where a function is being called from in MS Access? - vba

I am working on an MS Access database application that was created by someone else. There is one particular line of code (a Function) that will randomly get called and I have no idea why it is being called or what it does. I have searched (ctrl+F) the entire project for something that calls this function but I can't find it. How can I find out why this Function is being called? (See below). Thank you!
Public Function Concat(strIOSC As String, strFeature As String) As String
Static strLastIOSC As String
Static strFeatures As String
If strIOSC = strLastIOSC Then
strFeatures = strFeatures & ", " & strFeature
Else
strLastIOSC = strIOSC
strFeatures = strFeature
End If
Concat = strFeatures
End Function

If you have only searched the scripts and modules, then your scope is too narrow.
A public function like this can also be used in expressions, so you need to check queries, reports, form controls, macros, and possibly even tables if you use calculated fields. Depending on the size of the database, and how often the function is called, you can either search manually in a targeted way or possibly use a public sub to output something searchable. This sub can get you started. I think it outputs every possible location for expressions. Unfortunately, each object will have its own text file which will need to be searched separately unless you build a sub to do that too.
As for what your function does, it looks like it logs each input using the Static strLastIOSC variable, compares to the arguments passed on the second function call, and if they match it concatenates the two strFeature inputs together and outputs the result.
So basically the first argument tells the function whether this is the beginning of a new concatenation instance, or the continuation of an existing instance. The second argument is the item to be concatenated.
The Static keyword means that the value is stored even after the function runs so it can compare the last call with the current call to determine whether to add the second argument to the one saved from before, or clear the memory and prepare for a new concatenation.
Given its design, it's probably being used in a query/report/form, where strIOSC is likely a primary key field or a field in a GROUP BY.

Related

VBA - Compile Error: Expected Function or Variable

To start, I am not a programmer. Im just someone trying to make a document that has an "invoice" number that increases on the document that goes up every time I print the document. I found a macro code online but I keep getting the
Compile error
I'll attach a snap shot of what im getting with the piece that keeps screwing up high lighted.
Is there something I'm doing wrong?
To expand on braX's answer...
That's the syntax for assigning the return value of a Function or Property Get member - namely, you are assigning to the procedure's identifier:
Public Function GetTotallyRandomNumber() As Long
GetTotallyRandomNumber = 4
End Function
Seems you mean to have a local variable named SerialNumber, however VBA already knows this identifier as the name of a Sub procedure named SerialNumber, and because a Sub procedure doesn't return anything, it can't legally be assigned like this.
Declare a local variable inside the procedure's scope, before the illegal assignment:
Dim SerialNumber As String
SerialNumber = System.PrivateProfileString(...)
And then your code will work... however I wouldn't recommend using the exact same name as the procedure.
My recommendation would be to name the local variable SerialNumber, and to rename the Sub procedure so that its name starts with a verb. Procedures do something, they're actions: find a meaningful name that describes what it does, and go with that.
Naming is hard though - if you can't find a simple name that describes what your procedure does, it's probably because it's doing too many things. Split it up into smaller, more focused procedures.
Public Sub PrintActiveDocumentAndAddSerialNumberBookmark()
You are treating a Function like a Subroutine. Subroutines do not return values.
If you want the routine to return a value, then change Sub to Function (at the top)
If you are not wanting it to return anything, then choose a different variable name instead of SerialNumber, or change the name of the Subroutine.
You cannot use a variable name that is the same as the name of the Subroutine.

VBA Assign value (only) to variable

I've hit a snag, and my searching hasn't helped so far.
I have a variant being passed into a function which I then intend to copy, perform some calculations, take another copy, perform some other calculations then compare the results of the two copies...
However, when I perform the calculations on one copy, the original variant is also manipulated... so after two copies, and two calculations I end up with 3 variants that are equal to each other and different to the original... Not what I intended.
I expect this is happening because when I use NewVar = OldVar I'm actually taking a reference to the original object. What I actually want, is to make an independent duplicate of the original object - i.e. copy the value of the variable similar to byval in a function delcaration.
My code is linked here: https://1drv.ms/x/s!AiPgb0BH-YZ_ga956eMmJbSdihGjyg.
If you put a break on line 67 of modMain, then watch CutList(1).QTY (the original variable), and CutTrial.RemainingCuts(1).QTY you'll see that both the QTY values decrement when you step through line 67... I want CutList(1).QTY to remain unchanged, and CutTrial.RemainingCuts(1).QTY to decement only.
Any suggestions?
Make sure the functions definition is as follows
Public function DoMagic(ByVal variable as something)
Don't use ByRef or as you found it will modify the reference.
If you are using an object, array or collection you will need to first copy it before using it.
Eg something like the following:
Public Function Clone() As Class1
Set Clone = New Class1
Clone.prop1 = prop1
Clone.PrivateThing = PrivateThing
End Function

A function that works in Vb.net a class, but not in a module

Writing in VS2012 using VB.Net, I am using a table of functions to interpret a script file.
The script file consists of lines like this:
#keyword,(string)
All the functions, and the dictionary, live in a module. All the functions have the same signature, so I can use them as delegates.
In a form, I read the script file and then process each line, extracting the keyword and the string accompanying it. Using the keyword, I get the address of the function I want from the dictionary, then call this function, passing it the string as a parameter. I use this syntax:
fndic(keyword)(string)
This works fine, finding the right function and executing it properly, except...
One of the functions puts text on a label:
Public Function fhdr(s As String) As Boolean
Form1.Header.Text = s
End Function
Only it doesn't. So to see if it works, I put in an extra line:
Public Function fhdr(s As String) As Boolean
MessageBox.Show("hdr " + Form1.Header.Text + " new " + s)
Form1.Header.Text = s
End Function
The message box appears, but the text still doesn't. So I put this function in the class of the form, and now it works!
So my question is – why does the function alter the form when it's in the form's code, but not when it's in the module?
Is there some arcane statement I must put in the module to make it aware of the form? Or should I just dump all the code into the form, making a big unlovely stew with 40 functions in it?

Integer in VB.net turns into String in VBA

I have a list of objects that are "keys" into a table - the keys are simply the items in the first column. They can be Integers or Strings, depending on what DB table we read it from. Since we use them a lot, we cache that column in an ArrayList called "Keys".
We wrote cover methods to return Row, one that uses strings and the other integers. If you call the integer version it simply returns that row by index. If you call the string, it looks down Keys for a match, and uses that index to pull out the row.
Ok, so now I pass Keys to Excel, pull out one of them in a loop, and ask it what it is...
TypeName(DB.Keys(i))
And the object returns...
Long
Great, the keys must have been integers! So now I'll try to get the row for that key, by calling the accessor method, Row...
DB.Row(DB.Keys(i))
And it calls into the version that takes a String.
Whoa!
Can anyone think of a reason that VBA calling back into our VB.net DLL ends up calling the wrong accessor?
ADDED CODE: I can't figure out how to put the code in as a reply, so I'm editing this. Here is the code in the VB.net class:
Public Function Row(ByVal K As String) As DataRow
Dim R As DtaRow = DB.Tables(0).Rows(K)
Return New DataRow(R)
End Function
Public Function Accounts(ByVal K As Integer) As DataRow
Dim R As DtaRow = DB.Tables(0).Rows(K)
Return New DataRow(R)
End Function
If you're wondering, there's two versions of Rows, which take strings or ints.
This code works perfectly from VB.net. You can ask for a row by key string or by the row number, that invokes the proper Row, which calls the proper Rows, and out comes the proper answer.
But in VBA, it always calls the method with the string input. If I rename it to RowIHATEYOUALL then I can call the Integer version just fine. But when there are two of them, differing only in signature, no such luck.
And the A and i (see comments) was my typo.
The interop layer does not support overloaded methods. Whenever you call Row, the first declared method with that name is used. The .NET overload resolution algorithm does not apply here.
Other overloads are exposed to VBA as Row_2, Row_3, etc. Thus, the following code should do what you expect:
DB.Row_2(DB.Keys(i))
This implicit dependency on the order of declaration has a high potential for error. Thus, I would suggest to either
give the methods unique names if they are called from VBA,
or, if you want to retain the "nice" overloaded version for .NET, add compatibility methods for VBA:
<Obsolete("Compatibility method for VBA, use Row instead.")>
Public Function RowByKeyVBA(ByVal K As String) As DataRow
Return Row(K)
End Function
<Obsolete("Compatibility method for VBA, use Row instead.")>
Public Function RowByNumberVBA(ByVal K As Integer) As DataRow
Return Row(K)
End Function
Further information can be found in the following question:
COM->.NET - can't access overloaded method
Following Heinzi's notes (above) I fixed this by making three method signatures for each call, one takes an Object and then attempts to figure out what it is, the others take the String and Integers. Within VB/C#/etc the proper String or Integer methods get called as expected, from VBA the Object version is called, as Heinzi noted
This causes the very minor issue that a user may have a "number like value" that is actually a String. For instance, the array keys might be "User3232" or "3232", both of which are String objects in the table. So you have to be careful, simply asking if the Object can be converted to an Int32 is not enough. This is unlikely to be something that effects most users.

is there a better way of retrieving my settings?

I'm not an IT professional so apologies if I've missed something obvious.
When writing a program I add a class SettingsIni that reads a text file of keys and values. I find this method really flexible as settings can be added or changed without altering any code, regardless of what application I have attached it to.
Here's the main code.
Public Shared Sub Load()
Using settingsReader As StreamReader = New StreamReader(System.AppDomain.CurrentDomain.BaseDirectory & "settings.ini")
Do While settingsReader.Peek > -1
Dim line As String = settingsReader.ReadLine
Dim keysAndValues() As String = line.Split("="c)
settingsTable.Add(keysAndValues(0).Trim, keysAndValues(1).Trim)
Loop
End Using
End Sub
Public Shared Function GetValue(ByVal key As String)
Dim value As String = settingsTable(key)
Return value
End Function
This allows you to use a setting within your code by calling the SettingsIni.GetValue method.
For example:
watcher = New FileSystemWatcher(SettingsIni.GetValue("inputDir"), "*" & SettingsIni.GetValue("extn")).
I find this makes my code esay to read.
My problem is the values in this case, inputDir and extn, are typed freehand and not checked by intellisense. I'm always worried that I may make a typo in an infrequently used branch of an application and miss it during testing.
Is there a best practice method for retrieving settings? or a way around these unchecked freehand typed values?
A best practice for your code example would be to use Constants for the possible settings.
Class Settings
Const inputDir as String = "inputDir"
Const extn as String = "extn"
End Class
watcher = New FileSystemWatcher(SettingsIni.GetValue(Settings.inputDir), "*" & SettingsIni.GetValue(Settings.extn))
I assume you are using VB.NET?
If so, there is the handy "Settings"-menu under "my project". It offers a way to store the settings for your program and retrieve them via "my.settings.YOURKEY". The advantage is, that type securtiy is enforced on this level.
Additionally, you can also store "resources" almost the same way - but resources are better suited for strings / pictures etc. But they are expecially good if you want to translate your program.
As for your current problem:
Store the path in the settings, this way you do not need to change alll your code immidiately but you can use your system and never misspell anything.
If it's a number you could do these 3 things:
Check if is numeric - using IsNumeric function
Check if it is whole number - using Int function, like: if Int(number)=number
Check for the valid range, like: if number>=lowerbound and number<=upperbound
It totally depends on you. You are the one to check almost all the things inside quotes, not the intellisense.
But you still use Try-Catch block:
Try
Dim value As String = settingsTable(key)
Return value
Catch ex As Exception
MsgBox(ex.ToString)
Return ""
End Try
So you will get an message box if you are trying to access a non-existing setting that you may have mistyped.