I am getting an error when declaring variables in VBA - vba

I'm trying to declare two variables with the values of each one, but there is an error "Declaration expected" in the lines where I assing the values of c and e. Why is this happening ? Am I declaring the variables wrong?
Dim c As Integer
Dim e As Integer
c = 0
e = 0
Thanks a lot for your feedback

You can declare the variables outside a procedure to give them module-wide scope but you can't assign a value to them outside a procedure. Therefore this is correct.
Dim c As Integer
Dim e As Integer
Sub MyProcedure()
c = 0 ' the value is zero by default
e = 0
End Sub
and this is correct, too.
Sub MyProcedure()
Dim c As Integer
Dim e As Integer
c = 0 ' the value is zero by default
e = 0
End Sub
In the latter case the variables cannot be used in other procedures in the same module without again being declared there locally.

Place the variables in a separate code module, and declare them as Public:
Public c As Integer
Public e As Integer
They will default to the value 0 (zero).
Now you can assign them values from any form or code module.

Related

How do I get this basic OOP code to work in Visual Basic?

Thanks for reading my question. I am elaborating a class in which, 3 values must be entered by keyboard, the attributes must be handled with properties, the class must have the constructor and the destructor, and the highest and lowest value must be displayed. I do not know how I should make my code work correctly, for this reason I need help from someone to review it and locate the error. And it shows me compilation error and when testing it and entering the first value, it shows me 'false' on the screen. Some parts are in Spanish, because, they ask me to do so, I hope it is not an inconvenience.
I am attaching my code below, I hope someone can help me thank you.
Public Class ValoresPorTeclado
Private Val1 As Integer
Private Val2 As Integer
Private Val3 As Integer
Public Sub New() 'Constructor
Dim p = Val1 And Val1 AndAlso Val3 = 0
End Sub
Protected Shared Sub Finalize() 'Destructor
End Sub
Public Property Valor() As Integer
Get
Return Valor
End Get
Set(ByVal num As Integer)
If num > 0 Then
Dim p = Val1 And Val2 AndAlso Val3 = num
ElseIf num < 0 Then
Dim p = Val1 And Val2 AndAlso Val3 = num
Else
End If
End Set
End Property
Public Sub CargarValores(valor As Integer)
Dim v As Integer
Console.Write("Ingrese el primer valor ")
v = Console.ReadLine()
valor = Val1
Console.Write("Ingrese el segundo valor")
valor = Val2
Console.Write("Ingrese el tercer valor")
valor = Val3
CargarValores(valor)
End Sub
Public Sub Calcular(ByVal v As Integer)
Dim f, Compare As Integer
For f = 1 To 100
Compare = f > v AndAlso f < v
Dim Comapare As Boolean = Nothing
Console.Write(Comapare)
Console.Write("-")
Next
Console.WriteLine()
End Sub
End Class
Sub Main()
Dim Valores As New ValoresPorTeclado()
Valores.CargarValores()
Console.ReadKey()
End Sub
End Module
There are so many errors that it is hard to tell you how to fix them.
You should start by setting
Option Strict On
at the top of your code or in the project properties. This generates even more compiling errors. This is good, because it often reveals errors in your code that prevent it from running right.
There is an End Module without matching Module moduleName. Write
Module Program
Sub Main()
...
End Sub
End Module
The constructor declares a local variable p. This variable is only accessible in the constructor itself and is therefore useless.
I know, you have been told to do so, but finalizers in .NET are not a good idea unless there is a compelling reason for them, like releasing resources. The documentation for C# sharp says (the same is true for VB):
Empty finalizers should not be used. When a class contains a finalizer, an entry is created in the Finalize queue. When the finalizer is called, the garbage collector is invoked to process the queue. An empty finalizer just causes a needless loss of performance.
Property Valor makes no sense.
It returns itself in the getter, thus leading to an endless recursion.
The setter makes the difference between two cases num > 0 and num < 0, but the two cases then do exactly the same.
The two cases in the setter declare and initialize a local variable that is never used.
The condition Val1 And Val2 AndAlso Val3 = num is wrong. It should probably be Val1 = num And Val2 = num And Val3 = num (but I do not see what its purpose is in this context).
Either use And or the short circuiting version AndAlso. Mixing the two makes no sense.
Since I have no idea what the intention of this property is, I cannot tell you how to fix it.
Sub CargarValores has an unused Integer parameter. It is useless. You have only one ReadLine(). How is it supposed to read 3 values from the user? You assign Val1, Val2 and Val3 to valor. Instead, you should assign the values you read from the user to Val1, Val2 and Val3.
CargarValores calls itself at the end, thus again creating an endless recursion.
You wrote "3 values must be entered by keyboard, the attributes must be handled with properties", but you have declared 3 fields instead. Make them properties.
Trying to put things together.
Declare the class as
Option Strict On
Public Class ValoresPorTeclado
Public Property Val1 As Integer
Public Property Val2 As Integer
Public Property Val3 As Integer
... the methods go here
End Class
Since you must input 3 values, declare a function prompting the user, reading the input as string and converting this input into an integer
Private Function PromptForValue(prompt As String) As Integer
Console.Write(prompt + ": ")
Dim input = Console.ReadLine()
Return Integer.Parse(input)
End Function
This function can be used in Sub CargarValores to load the 3 values and set the properties
Public Sub CargarValores()
Val1 = PromptForValue("Ingrese el primer valor")
Val2 = PromptForValue("Ingrese el segundo valor")
Val3 = PromptForValue("Ingrese el tercer valor")
End Sub
You must get the lowest and the highest values. Create functions for this
Public Function LowestValue() As Integer
Dim lowest As Integer = Val1
If Val2 < lowest Then
lowest = Val2
End If
If Val3 < lowest Then
lowest = Val3
End If
Return lowest
End Function
Public Function HighestValue() As Integer
Dim highest As Integer = Val1
If Val2 > highest Then
highest = Val2
End If
If Val3 > highest Then
highest = Val3
End If
Return highest
End Function
Now you can write the Main method as
Module Program
Sub Main()
Dim Valores As New ValoresPorTeclado()
Valores.CargarValores()
Dim lowest As Integer = Valores.LowestValue()
Dim highest As Integer = Valores.HighestValue()
Console.WriteLine($"The lowest value is {lowest}")
Console.WriteLine($"The highest value is {highest}")
Console.ReadKey()
End Sub
End Module
Constructor and destructor can be left empty. If it was not a requirement in your assigment, I would drop them.
I would like to see your class independent from the user interface.
As previously stated your code was a bit of a mess.
This simple class contains 2 constructors, 3 properties and a single method.
The parametrized constructor sets all three properties. We add back the default constructor so an application using the class can set properties individually if it wishes.
The Function returns the highest of the 3 Properties by using the .Max method of
List(Of T).
Public Class ValoresPorTeclado
Public Property Valor1 As Integer
Public Property Valor2 As Integer
Public Property Valor3 As Integer
Public Sub New()
End Sub
Public Sub New(Int1 As Integer, Int2 As Integer, Int3 As Integer) 'Constructor
Valor1 = Int1
Valor2 = Int2
Valor3 = Int3
End Sub
Public Function GetMaxInteger() As Integer
Dim ListOfValues As New List(Of Integer)
ListOfValues.AddRange({Valor1, Valor2, Valor3})
Return ListOfValues.Max
End Function
End Class
To use the class in a Console application...
We are using the Integer.TryParse which returns True or False and also sets the value of i to the converted string if it returns True.
After we get the user input we create a new instance of the ValoresPorTeclado class calling the parametrized constructor. Then we can call the Function on this instance, val.
Module Module1
Sub Main()
Dim Counter As Integer
Dim IntegerList As New List(Of Integer)
Do Until Counter = 3
Console.WriteLine("Please enter a whole number")
Dim i As Integer
If Integer.TryParse(Console.ReadLine, i) Then
IntegerList.Add(i)
Counter += 1
Else
Console.WriteLine("Not a valid number. Try again.")
End If
Loop
Dim val As New ValoresPorTeclado(IntegerList(0), IntegerList(1), IntegerList(2))
Console.WriteLine($"The highest number you entered is {val.GetMaxInteger}")
Console.ReadKey()
End Sub
End Module

VB.NET Boxing weirdness

I can't understand what is happening with the following code in VB.NET. When I run this code:
Public Function test() As Boolean
Dim a As Integer = 1
Dim b As Object = a
Dim c As Object = b
Return Object.ReferenceEquals(b, c)
End Function
Then the function returns True. However, if I run this:
Structure TTest
Dim i As Integer
Dim tid As Integer
Sub New(ByVal _i As Integer, ByVal _tid As Integer)
i = _i
tid = _tid
End Sub
End Structure
Public Function test_2() As Boolean
Dim a As New TTest(1, 1)
Dim b As Object = a
Dim c As Object = b
Return Object.ReferenceEquals(b, c)
End Function
Then it returns False. In both functions, I declare two value type variables, an Integer on the first and a custom Structure on the second one. Both should be boxed upon object assignment, but in the second example, it seems to get boxed into two different objects, so Object.ReferenceEquals returns False.
Why does it work this way?
For primitive types, .Net is able to re-use the same "box" for the same values, and thus improve performance by reducing allocations.
Same with strings, it's .NET way to optimize thing. But as soon as you use it, the reference will change.
Sub Main()
Dim a As String = "abc"
Dim b As String = "abc"
Console.WriteLine(Object.ReferenceEquals(a, b)) ' True
b = "123"
Console.WriteLine(Object.ReferenceEquals(a, b)) ' False
Console.ReadLine()
End Sub

visual basic multiple input boxes i only need 1

Why do I keep getting two input boxes instead of one? What am I doing wrong? Is it how I am passing values through functions? If so, how can I fix this?
Private Sub Calculate_Click(sender As Object, e As EventArgs) Handles Calculate.Click
'Dim ready_ship As Integer = GetInStock()
Dim display_spools As Integer = ReadyToShip()
Dim display_backOrders As Integer = BackOrdered()
lbl_rship.Text = display_spools.ToString()
lbl_backo.Text = display_backOrders.ToString()
End Sub
Function GetInStock() As Integer
Dim amount_Spools As String = Nothing
amount_Spools = InputBox(" Enter the number of spools currently in stock: ")
Return CInt(amount_Spools)
End Function
Function ReadyToShip() As Integer
Dim ready_ship As Integer = GetInStock()
Dim a As Integer
a = CInt(ready_ship)
Return a
End Function
Function BackOrdered() As Integer
Dim b As Integer = ReadyToShip()
Dim c As Integer
c = b - CInt(TextBox1.Text)
Return c
End Function
End Class
Your Calculate_Click event is calling ReadyToShip() and BackOrdered() functions which are both going to GetInStock() function, which is displaying the input box. So it will be displayed twice.
This class would be better served using properties, they are easier to manage and will help avoid this kind of method duplication.

How can I assign a Variant to a Variant in VBA?

(Warning: Although it might look like one at first glance, this is not a beginner-level question. If you are familiar with the phrase "Let coercion" or you have ever looked into the VBA spec, please keep on reading.)
Let's say I have an expression of type Variant, and I want to assign it to a variable. Sounds easy, right?
Dim v As Variant
v = SomeMethod() ' SomeMethod has return type Variant
Unfortunately, if SomeMethod returns an Object (i.e., a Variant with a VarType of vbObject), Let coercion kicks in and v contains the "Simple data value" of the object. In other words, if SomeMethod returns a reference to a TextBox, v will contain a string.
Obviously, the solution is to use Set:
Dim v As Variant
Set v = SomeMethod()
This, unfortunately, fails if SomeMethod does not return an object, e.g. a string, yielding a Type Mismatch error.
So far, the only solution I have found is:
Dim v As Variant
If IsObject(SomeMethod()) Then
Set v = SomeMethod()
Else
v = SomeMethod()
End If
which has the unfortunate side effect of calling SomeMethod twice.
Is there a solution which does not require calling SomeMethod twice?
In VBA, the only way to assign a Variant to a variable where you don't know if it is an object or a primitive, is by passing it as a parameter.
If you cannot refactor your code so that the v is passed as a parameter to a Sub, Function or Let Property (despite the Let this also works on objects), you could always declare v in module scope and have a dedicated Sub solely for the purpose of save-assigning that variable:
Private v As Variant
Private Sub SetV(ByVal var As Variant)
If IsObject(var) Then
Set v = var
Else
v = var
End If
End Sub
with somewhere else calling SetV SomeMethod().
Not pretty, but it's the only way without calling SomeMethod() twice or touching its inner workings.
Edit
Ok, I mulled over this and I think I found a better solution that comes closer to what you had in mind:
Public Sub LetSet(ByRef variable As Variant, ByVal value As Variant)
If IsObject(value) Then
Set variable = value
Else
variable = value
End If
End Sub
[...] I guess there just is no LetSet v = ... statement in VBA
Now there is: LetSet v, SomeMethod()
You don't have a return value that you need to Let or Set to a variable depending of its type, instead you pass the variable that should hold the return value as first parameter by reference so that the Sub can change its value.
Dim v As Variant
For Each v In Array(SomeMethod())
Exit For 'Needed for v to retain it's value
Next v
'Use v here - v is now holding a value or a reference
You could use error trapping to reduce the expected number of method calls. First try to set. If that succeeds -- no problem. Otherwise, just assign:
Public counter As Long
Function Ambiguous(b As Boolean) As Variant
counter = counter + 1
If b Then
Set Ambiguous = ActiveSheet
Else
Ambiguous = 1
End If
End Function
Sub test()
Dim v As Variant
Dim i As Long, b As Boolean
Randomize
counter = 0
For i = 1 To 100
b = Rnd() < 0.5
On Error Resume Next
Set v = Ambiguous(b)
If Err.Number > 0 Then
Err.Clear
v = Ambiguous(b)
End If
On Error GoTo 0
Next i
Debug.Print counter / 100
End Sub
When I ran the code, the first time I got 1.55, which is less than the 2.00 you would get if you repeated the experiment but with the error-handling approach replaced by the naïve if-then-else approach you discussed in your question.
Note that the more often the function returns an object, the less function calls on average. If it almost always returns an object (e.g. that is what it is supposed to return but returns a string describing an error condition in certain cases) then this way of doing things will approach 1 call per setting/ assigning the variable. On the other hand -- if it almost always returns a primitive value then you will approach 2 calls per assignment -- in which case perhaps you should refactor your code.
It appears that I wasn't the only one with this issue.
The solution was given to me here.
In short:
Public Declare Sub VariantCopy Lib "oleaut32.dll" (ByRef pvargDest As Variant, ByRef pvargSrc As Variant)
Sub Main()
Dim v as Variant
VariantCopy v, SomeMethod()
end sub
It seems this is similar to the LetSet() function described in the answer, but I figured this'd be useful anyway.
Dim v As Variant
Dim a As Variant
a = Array(SomeMethod())
If IsObject(a(0)) Then
Set v = a(0)
Else
v = a(0)
End If

.NET - Block level scope

Please have a look at the code below:
Public Class TestClass
Public TestProperty As Integer
End Class
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object,
ByVal e As System.EventArgs) Handles Me.Load
Dim i As Integer
Dim j As Integer
For j = 0 To 2
For i = 0 To 10
Dim k As Integer
Dim tc As TestClass
tc = New TestClass
tc.TestProperty = tc.TestProperty + 1
k = k + 1
Next
Next
End Sub
End Class
There is a new object (called tc) created on every iteration of the FOR loop, so tc.TestProperty is always 1. Why is this not the case with variable k i.e. the value of k increments by one on every iteration? I realise this is probably to do with how value types and reference types are dealt with, but I wanted to check.
It's because when something is defined as block level it applies to the entire block level, regardless of loops. normally with control logic like an IF block statement the scope starts and ends and no code lines repeat.
Inside a loop structure the variable is defined inside that block, even though the Dim statement appears to be called multiple times it is not, it is not actually an executable statement (just a definition and reservation of a placeholder as mentioned above in one comment)
To cause it to behave in the same way as "tc" you also need to initialize it in a similar way. (the assignment to 0 would occur each loop, not the definition)
Dim k As Integer = 0
Alternately if you change how your dealing with tc it would behave the same way as k where it is in block scope the entire time inside the loop. In the below example tc is not redefined each loop either.
Dim tc as TestClass
if tc is nothing then tc = New TestClass
You would have to Dim k As Integer = 0 to keep it at 1.
This is because Dim k As Integer retains it's value, while Dim k As Integer = 0 "declares and initializes" it.
Specifically: "If you alter the value but then return to the Dim statement, your altered value is replaced by the value supplied in the Dim statement."
Actually, I don't know why it doesn't seem go out of scope. Maybe without the New keyword it's using the same block of memory.
As implied by the title of this question, you're querying the scope versus the lifetime of the variable.
The scope of the local variables k and tc is the inner For loop. The lifetime is the whole of the Sub.
If you adjusted the tc = New TestClass to If tc Is Nothing Then tc = New TestClass (and ignored the warning that causes), you should then see the tc.TestProperty increment too.
"Dim k As Integer" isn't actually translate into any code except "space reservation" (that is surely made at compile time). So the application does not pass on that sentence 10 times.
As a matter of proof, you can not put a trace bullet on that line of code !
On the other hand, your code create on each loop a fresh new object TestClass (holding a brand new variable "TestProperty) and assign it to the variable "tc". The previous object is lost and carbage collected anytime soon.