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):
Related
I want my program to take a variable, and find letters A-Z. I have made this section of my program in a module to be shared between 2 different forms.
variables are passed from form1 and are processed by the module and then sent back again to form1. the problem is I think some sort of bug in the code but I cant identify it.
Public Function UPCASES(ByRef password1, points) As Boolean
Dim intersection As IEnumerable(Of Char)
intersection = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Intersect(password1)
'System.StackOverflowException error ^^^^^^^^^^^^^^^^^^^^^^^^^
If intersection.Count() = 1 Then
points = 5
Else
points = 0
End If
Return UPCASES(password1, points)
End Function
You are calling the method itself at the method end, that causes the StackOverflowException:
Return UPCASES(password1, points)
I guess this method should check if the password contains uppercase letters, then use:
Dim containsUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Intersect(password1).Any()
So no need to create a method just for this one-liner, if you need a method:
Public Function ContainsUpperCaseLetter(password1 As String) As Boolean
Return "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Intersect(password1).Any()
End Function
Side-note: you should change your default project settings to use Option Strict ON(Off is default unfortunately). Then you will be able to write much more safe, robust and performant code after you have learned a lot about .NET types because you have to fix the compiler errors.
Compile Error:
Compile Error: Only user-defined types defined in public object
modules can be coerced to or from a variant or passed to a late-bound
functions.
I'm new to VBA and I was tasked with debugging some code for a custom screen in Dynamics SL. The first thing I did was to see if it compiled and I got the above message.
When I read the built-in help reference I found the following for the above error:
You attempted to use a public user defined type as a parameter or
return type for a public procedure of a class module, or as a field of
a public user defined type. Only public user defined types that are
defined in a public object module can be used in this manner.
I also went through these similar questions:
How to put user defined datatype into a Dictionary
Only user-defined type defined in public object modules can be coerced when trying to call an external VBA function
They have the same error but I don't see a collection object that the above two questions focused on.
If you may have any idea what may be causing this error please don't hesitate to suggest it.
Code:
Private Sub cpjt_entity_Chk(ChkStrg As String, retval As Integer)
Dim ldDate As Sdate
Dim xStrDailyPost As Sdate
ldDate.val = GetObjectValue("cpe_date")
'xStrDailyPost = DateToStr(ldDate)
'Call MsgBox("Daily Post Date: " & xStrDailyPost, vbOKOnly, "TEST")
serr1 = SetObjectValue("cld_id08", xStrDailyPost) <- Error highlights "xStrDailyPost"
End Sub
Definition for SetObjectValue:
Declare Function SetObjectValue Lib "swimapi.dll" Alias "VBA_SetObjectValue" (ByVal ctlname$, newval As Variant) As Integer
Thank you in advance!
You are probably working with code that was originally written with the Dynamics SL (actually it was Solomon IV at the time) Basic Script Language (BSL) macro language instead of VBA.
Regardless... the fix is, pass results of the "val" method of your xStrDailyPost instance of SDate. SO the code should look like:
serr1 = SetObjectValue("cld_id08", xStrDailyPost.val)
I've not actually tested this but I'm pretty sure this will address your issue.
If you want a little more background, "Sdate" is really just a very thin wrapper of an integer (actually I think it's a short, but I've never found I really needed to know for sure). the "Val" method returns the underlying integer in the SDate variable.
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
I want to do this but it won't compile:
Public MyVariable as Integer = 123
What's the best way of achieving this?
.NET has spoiled us :)
Your declaration is not valid for VBA.
Only constants can be given a value upon application load. You declare them like so:
Public Const APOSTROPHE_KEYCODE = 222
Here's a sample declaration from one of my vba projects:
If you're looking for something where you declare a public variable and then want to initialize its value, you need to create a Workbook_Open sub and do your initialization there.
Example:
Private Sub Workbook_Open()
Dim iAnswer As Integer
InitializeListSheetDataColumns_S
HideAllMonths_S
If sheetSetupInfo.Range("D6").Value = "Enter Facility Name" Then
iAnswer = MsgBox("It appears you have not yet set up this workbook. Would you like to do so now?", vbYesNo)
If iAnswer = vbYes Then
sheetSetupInfo.Activate
sheetSetupInfo.Range("D6").Select
Exit Sub
End If
End If
Application.Calculation = xlCalculationAutomatic
sheetGeneralInfo.Activate
Load frmInfoSheet
frmInfoSheet.Show
End Sub
Make sure you declare the sub in the Workbook Object itself:
Just to offer you a different angle -
I find it's not a good idea to maintain public variables between function calls. Any variables you need to use should be stored in Subs and Functions and passed as parameters. Once the code is done running, you shouldn't expect the VBA Project to maintain the values of any variables.
The reason for this is that there is just a huge slew of things that can inadvertently reset the VBA Project while using the workbook. When this happens, any public variables get reset to 0.
If you need a value to be stored outside of your subs and functions, I highly recommend using a hidden worksheet with named ranges for any information that needs to persist.
Sure you know, but if its a constant then const MyVariable as Integer = 123 otherwise your out of luck; the variable must be assigned an initial value elsewhere.
You could:
public property get myIntegerThing() as integer
myIntegerThing= 123
end property
In a Class module then globally create it;
public cMyStuff as new MyStuffClass
So cMyStuff.myIntegerThing is available immediately.
Little-Known Fact: A named range can refer to a value instead of specific cells.
This could be leveraged to act like a "global variable", plus you can refer to the value from VBA and in a worksheet cell, and the assigned value will even persist after closing & re-opening the workbook!
To "declare" the name myVariable and assign it a value of 123:
ThisWorkbook.Names.Add "myVariable", 123
To retrieve the value (for example to display the value in a MsgBox):
MsgBox [myVariable]
Alternatively, you could refer to the name with a string: (identical result as square brackets)
MsgBox Evaluate("myVariable")
To use the value on a worksheet just use it's name in your formula as-is:
=myVariable
In fact, you could even store function expressions: (sort of like in JavaScript)
(Admittedly, I can't actually think of a situation where this would be beneficial - but I don't use them in JS either.)
ThisWorkbook.Names.Add "myDay", "=if(isodd(day(today())),""on day"",""off day"")"
Square brackets are just a shortcut for the Evaluate method. I've heard that using them is considered messy or "hacky", but I've had no issues and their use in Excel is supported by Microsoft.
There is probably also a way use the Range function to refer to these names, but I don't see any advantage so I didn't look very deeply into it.
More info:
Microsoft Office Dev Center: Names.Add method (Excel)
Microsoft Office Dev Center: Application.Evaluate method (Excel)
As told above, To declare global accessible variables you can do it outside functions preceded with the public keyword.
And, since the affectation is NOT PERMITTED outside the procedures, you can, for example, create a sub called InitGlobals that initializes your public variables, then you just call this subroutine at the beginning of your statements
Here is an example of it:
Public Coordinates(3) as Double
Public Heat as double
Public Weight as double
Sub InitGlobals()
Coordinates(1)=10.5
Coordinates(2)=22.54
Coordinates(3)=-100.5
Heat=25.5
Weight=70
End Sub
Sub MyWorkSGoesHere()
Call InitGlobals
'Now you can do your work using your global variables initialized as you wanted them to be.
End Sub
You can define the variable in General Declarations and then initialise it in the first event that fires in your environment.
Alternatively, you could create yourself a class with the relevant properties and initialise them in the Initialise method
This is what I do when I need Initialized Global Constants:
1. Add a module called Globals
2. Add Properties like this into the Globals module:
Property Get PSIStartRow() As Integer
PSIStartRow = Sheets("FOB Prices").Range("F1").Value
End Property
Property Get PSIStartCell() As String
PSIStartCell = "B" & PSIStartRow
End Property
there is one way to properly solve your question. i have the same concern with you for a long time. after searching and learning for a long time, finally i get a solution for this kind of question.
The solution is that no need to declare the variable and no need to set value to the variable, and even no need VBA code. Just need the "named range" in excel itself.
For example, the "A1" cell content is "hello, world". and we define the "A1" cell a name as "hello", that is, the "A1" cell have a name now, it's called "hello".
In VBA code, we just need use this method [hello], then we can get the "A1" value.
Sub test()
msgbox [hello]
end sub
the msgbox will show "Hello, word".
this way, we get a global variable without any declaration or assignment. it can be used in any Sub or Function.
we can define many named range in excel, and in VBA code we just use [] method to get the range value.
in fact, the [hello] is a abbreviation of the function Evaluate["Hell"], but it's more shorter.
It's been quite a while, but this may satisfy you :
Public MyVariable as Integer: MyVariable = 123
It's a bit ugly since you have to retype the variable name, but it's on one line.
What is an intrinsic value type, and what is the difference to non-intrinsic value types?
I couldn't find documentation about the effect of the option "Function returning intrinsic value type without return value." in Visual Studio's VB.Net project properties' Compile page.
You're right, there doesn't seem to be any documentation here.
Consider this code:
Module Module1
Sub Main()
Console.WriteLine("Foo() is {0}", Foo())
Console.ReadKey()
End Sub
Function Foo() As Integer
End Function
End Module
With the default project properties, which have "Function returning intrinsic value type without return value" set to Warning, this compiles with this warning:
warning BC42353: Function 'Foo' doesn't return a value on all code paths. Are you missing a 'Return' statement?
and outputs
Foo is 0
By setting That project property to Error, we can make this warning halt compilation with an error.
The 'intrinsic' part comes into play if we change the code to this:
Module Module1
Sub Main()
Console.WriteLine("Foo() is {0}", Foo())
Console.ReadKey()
End Sub
Function Foo() As Bar
End Function
End Module
Structure Bar
Public a As Integer
End Structure
Now, even though Bar is a value type, the code compiles with no warning whatever that project property is set to. We can therefore conclude that Integer is an 'intrinsic' value type, but our Bar is not.
What none of this tells us is what counts as an 'intrinsic' value type. Googling around, I found this page which tells me that if I fire up the Object Browser, right click in the left-hand pane and tell it to Group By Object Type, I see this:
which I think is the best we're going to get.
"intrinsic" can be taken as "built-in" in this case.
And it seems not all that relevant, you are simply missing a return.