VBA assigns incorrect value to variable - vba

I have a function in VBA, which is a part of a bigger setup. The function resides inside a Class Module, and is basically just a glorified subtraction. I wondered why I got some weird results, so I (over-)simplified the function for debugging purposes. It turns out, that one of the variables isn't assigned the value that it should, but rather some seemingly random value. How can a simple assign go so wrong?
And even weirder, why does it not always it assigns the incorrect value? It only happens sometimes. Other times it is correct. And occationally it seems like nothing is evaluated at all, and the function just returns 0 (zero).
From all I can see it cannot be an issue with my code, but rahter with the way VBA works (behind the scenes). But as long as I do not understand it, it is quite difficult to mitigate.
Code:
Public Property Get MySubtractionFunction() As Double
Dim tmpValue1 As Double
Dim tmpValue2 As Double
Dim tmpOutput As Double
'When debugging, sometimes CDbl(Me.Value2) evaluates to approximately 18.000
'However tmpValue2 evaluates to approximately 10.000
tmpValue1 = CDbl(Me.Value1)
tmpValue2 = CDbl(Me.Value2)
tmpOutput = tmpValue1 - tmpValue2 'Breakpoint is set at this line
tmpOutput = Application.WorksheetFunction.Min(tmpOutput , tmpValue1)
'Return output
MySubtractionFunction= tmpOutput
End Property
Update 1
When I hover the mouse over Me.Value2 before reaching the breakpoint, it actually shows the value that is assigned to tmpValue2. If I then remove the mouse, and hover back over Me.Value2 again, then it shows a different value. How can a property value just change like that, without any code being executed?
Update 2
Maybe I should mention that the problem only arises when I use the Class Object inside a loop. It is called like this:
For i = 1 To 1000
Dim myObject As myClass
Set myObject = New myClass
'Some initialization
result = myObject.MySubtractionFunction
'A bunch of other stuff
Set myObject = Nothing
Next i

All computer languages have a huge problem, when they are dealing with doubles (floating points). Thus, in C#, you should use decimal to avoid this problem. In Excel, you should round.
Take a look at what Microsoft says about it:
https://support.microsoft.com/en-us/help/78113/floating-point-arithmetic-may-give-inaccurate-results-in-excel

Solution
As I mentioned, the property I had an issue with, was the last in a chain of many. All calling other properties of the class module (except the first in the chain). The problem originated quite early, and chained through all the steps.
The reason it was so difficult to identify, was that the property value which was initially incorrect, was updated when I hovered the mouse over the variable to inspect the variable, in debugging mode.
The solution was to expand all the steps in the chain, in a manner similar to that in the original post. This is not always required, since the problem only showed when I ran a multiple of calculations rapidly. But if anyone expeirence similar problems, I would suggest you try this fix.
This did not work:
Public Property Get myProperty() As Double
myProperty = Me.someOtherProperty + Me.aThirdProperty
End Property
This did:
Public Property Get myProperty() As Double
Dim tempSomeOtherProperty As Double
Dim tempAThirdProperty As Double
Dim tempResult As Double
tempSomeOtherProperty = Me.someOtherProperty
tempAThirdProperty = Me.aThirdProperty
tempResult = tempSomeOtherProperty + tempAThirdProperty
myProperty = tempResult
End Property

Related

Variable in a form won't keep its value after being used in the call to another form

I have a form with a variable in it called "VigilTable." This variable gets its value from the calling string OpenArgs property.
Among other things, I use this variable in the call string when opening other forms.
But it only works the first call.
MsgBox VigilTable before the call will always show "Spring2022" or whatever on the first call but always comes up blank on succeeding calls (and I get "invalid use of NULL" when the called form attempts to extract the value from OpenArgs). The variable is dimmed as String in the General section of the form's VBA code.
So what's happening here? And can I fix it?
Thanks.
Ok, so you delcared a variable at the form level (code module) for that given form.
and we assume that say on form load, you set this varible to the OpenArgs of the form on form load.
So, say like this:
Option Compare Database
Option Explicit
Public MyTest As String
Private Sub Form_Load()
MyTest = Me.OpenArgs
End Sub
Well, I can't say having a variable helps all that much, since any and all code in that form can use me.OpenArgs.
but, do keep in mind the following:
ONLY VBA code in the form can freely use that variable. It is NOT global to the applcation, but only code in the given form.
However, other VBA code outside of the form can in fact use this variable. But ONLY as long as the form is open.
So, in the forms code, you can go;
MsgBox MyTest
But, for VBA outside of the form, then you can get use of the value like this:
Msgbox forms!cityTest.MyTest
However, do keep in mind that any un-handled error will (and does) blow out all global and local variables. So, maybe you have a un-handled error.
Of course if you compile (and deploy) a compiled accDB->accDE, then any errors does NOT re-set these local and global variables.
but, for the most part, that "value" should persist ONLY as long as the form is open, and if you close that form, then of course the values and variables for that form will go out of scope (not exist).
Now, you could consider moving the variable declare to a standard code module, and then it would be really global in nature, but for the most part, such code is not recommended, since it hard to debug, and such code is not very modular, or even easy to maintain over time.
So, this suggests that some error in VBA code is occurring, and when that does occur, then all such variables are re-set (but, the noted exception is if you compile down to an accDE - and any and all variables will thus persist - and even persist their values when VBA errors are encountered.
For a string variable, a more robust solution not influenced by any error, should be writing/reading in/from Registry. You can use the, let as say, variable (the string from Registry) from any workbook/application able to read Registry.
Declare some Public constants on top of a standard module (in the declarations area):
Public Const MyApp As String = "ExcelVar"
Public Const Sett As String = "Settings"
Public Const VigilTable As String = "VT"
Then, save the variable value from any module/form:
SaveSetting MyApp, Sett, VigilTable , "Spring2022" 'Save the string in Regisgtry
It can be read in the next way:
Dim myVal as String
myVal = GetSetting(MyApp, Sett, VigilTable , "No value") 'read the Registry
If myVal = "No value" Then MsgBox "Nothing recorded in Registry, yet": Exit Sub
Debug.print myVal
Actually, this proved not to be the the answer at all.
It was suggested that I declare my variables as constants in the Standard module but I declared them as variables. It appeared at first to work, at least through one entire session, then it ceased to work and I don't know why.
If I declare as constants instead, will I still be able to change them at-will? That matters because I re-use them with different values at different times.
I didn't do constants but declaring VigilName in the Standard module and deleting all other declarations of it fixed both problems.
While I was at it I declared several other variables that are as generally used and deleted all other declarations of them as well so that at least they'll be consistently used throughout (probably save me some troubleshooting later.
Thanks to all!

Circular Dependency Error when using Enumerations

I want to assign certain time weightage values to the variables of a data set. So I am trying to use Enumerations/Constants so to improve my code quality and make it easy to maintain. But on compiling my project, it throws in a Circular Dependencies Between Modules error.
What I have understood is, one is allowed to use only constants [Eg: 2,3.14,56....], in the truest sense of the word. Bottom line, if I can't use Either Enumerations or Constants. Then how can I achieve my objective of keeping my weightages code-block, in a way that any changes in them is reflected everywhere in my project than me having to do a find and update manually every instance.
What I am getting at, is to have a global variable that can be accessed throughout the project and is dynamic.
Private Const Wtage As Double = ConvTohr(34) 'Error happens here
Enum Weightage
Var1_Weightage = ConvTohr(3) 'Error happens here
Var2_Weightage = ConvTohr(11)
Var3_Weightage = ConvTohr(2)
var4_Weightage = ConvTohr(9)
var5_Weightage = ConvTohr(0)
End Enum
Private Function ConvTohr(val As Integer) As Double
If val = 0 Then
ConvTohr = 0
Exit Function
End If
ConvTohr = Round((val / 60), 2)
End Function
The error message is incorrect: your code does not have any circular references.
This is more of a bug in the VBA interpreter: your code is still incorrect and invalid, but VBA is showing the wrong error message.
Given that VBA remains effectively frozen-in-time since 2002 (excepting 64-bit support in 2007), so don't expect any fixes, let alone any enhancements, ever (Though MS Office's COM automation tooling is slowly shifting to JavaScript (maybe Python? please?).
The actual problem with your code is threefold:
You cannot use a Function to initialize a Const value in VBA.
You cannot use a Function to define values for a VBA Enum either.
You cannot have Enum members typed as Double: VBA only supports Integer and Long values for Enum members.
Curiously, VBA does allow Const values to be typed as Double - but most other languages don't because Double is an IEEE-754 floating point type that does not have a machine-portable representation, but as VBA is only for Win32 on x86/x64 I guess that means Microsoft made it work given the very narrow gamut of hardware that VBA programs will run on.
Anyway, if you want "named values" typed as Double that you can use anywhere, then try this:
Create a new Module (not a Class Module).
Rename the Module from Module1 to Weightage.
Put this code in Weightage:
Private Function ConvTohr(val As Integer) As Double
ConvTohr = Round((val / 60), 2)
End Function
Public Property Get Var1_Weightage() As Double
Var1_Weightage = ConvTohr(3)
End Property
Public Property Get Var2_Weightage() As Double
Var2_Weightage = ConvTohr(11)
End Property
Public Property Get Var3_Weightage() As Double
Var3_Weightage = ConvTohr(2)
End Property
Public Property Get Var4_Weightage() As Double
Var4_Weightage = ConvTohr(9)
End Property
Public Property Get Var5_Weightage() As Double
Var5_Weightage = ConvTohr(0)
End Property
Screenshot proof:
(See output in the Immediate pane):

How to make my module move an object using value set in UserForm/

I'm trying to automatize some processes I often do at work. Unfortunately I have no clue about programming so I've been struggling a lot with this.
I have an userform that has a textbox where you're supposed to type in how much you want the object to be moved on X axis. There are multiple objects and I can't wait to experiment how to move them all in proper direction, but the issue is, I can't even move one.
I created a variable, but I can't seem to be able to use it with Object.Move.
Right now VBA tells me to assign the values to an array, so I did that, but I can't put variables into an array apparently.
I've tried simply declaring the variable as public, calling it let's say for example "Value", and using Object.Move (Value), 0
or
Object.Move Value(), 0
Tried both, because I was not sure which one is correct.
After many trials I finally skipped most of the previous errors I was getting, and now I'm stuck at trying to set up an Array using a variable from the UserForm.
Here's the code inside Userform
VBA
Public Sub TextBox1_Initialize()
M = TextBox1
Unload M
End Sub
And here's the code inside the module
VBA
Public M As Integer
Sub Move()
Dim XY(M, 0) As Integer
Object.Move XY
End Sub

dr.Item("Value1") has value but always return 0 when assigned to another variable

I need to modify some VB.net code. There is a strange problem that I am facing. I am retrieving value from a DataTable and trying to assign it to a variable. When I check the value of some column in QuickWatch window then it has value but when I assign it to a variable then 0 is returned to the variable. Below is the simple statement that is causing the problem.
Dim MyAmount As Double = Double.Parse(dr.Item("Amount").ToString)
In the QuickWatch window when I check dr.Item("Amount") then it has value 30.12 and after executing the above statement MyAmount has value 0. May be VB.net work somewhat different that I do not know?
Edit:
It is kind of wierd that above mentioned statement is not returning value. The following statement is running absolutely fine.
Dim tmpVar As String() = dr.Item("Amount").ToString.Split(".")
Latest Edit:
I think it has become more wierd. The problem does not seem to be related with dr.Item("Amount"). Suppose I want to store the current culture value in a variable by following code,
Dim CultureInformation As String = System.Globalization.CultureInfo.CurrentCulture.DisplayName
Now CutlureInformation variable after the statement is executed contains "nothing" but the DisplayName has value of English (United States). So I think the problem is somewhere else.
You should be using this syntax:
Dim MyAmount As Double = dr.Field(Of Double)("Amount")
I am not sure why you are getting this behavior - your line should work too.
You can also try this:
Dim MyAmount As Double = DirectCast(dr.Item("Amount"), Double)
When facing a weird issue like this, always try various options to achieve the same result, do your research and compare the outputs. It greatly helps to answer a question on StackOverflow.

What is the lifetime of a global variable in excel vba?

I've got a workbook that declares a global variable that is intended to hold a COM object.
Global obj As Object
I initalize it in the Workbook_Open event like so:
Set obj = CreateObject("ComObject.ComObject");
I can see it's created and at that time I can make some COM calls to it.
On my sheet I have a bunch of cells that look like:
=Module.CallToComObject(....)
Inside the Module I have a function
Function CallToComObject(...)
If obj Is Nothing Then
CallToComObject= 0
Else
Dim result As Double
result = obj.GetCalculatedValue(...)
CallToComObject= result
End If
End Function
I can see these work for a bit, but after a few sheet refreshes the obj object is no longer initialized, ie it is set to Nothing.
Can someone explain what I should be looking for that can cause this?
Any of these will reset global variables:
Using "End"
An unhandled runtime error
Editing code
Closing the workbook containing the VB project
That's not necessarily an exhaustive list though...
I would suggest a 5th point in addition to Tim's 4 above: Stepping through code (debugging) and stopping before the end is reached. Possibly this could replace point number 3, as editing code don't seem to cause global variable to lose their values.