How can I assign a value to a number of different variables in a collection using loops? - vb.net

I have a problem that has been bugging me for a while now. Consider this code:
Public Class Class1
Dim VariableList as New List(of Object) From {MainForm.X, MainForm.Y,
SettingsForm.Z, SettingsForm.Textbox1.Text} '...etc.
Sub SetToZero()
For Each Element in VariableList
Element = 0
Next
End Sub
Sub SetToCustomValue(value As Double)
For Each Element in VariableList
Element = value
Next
End Sub
Sub LoadValuesFromFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Readline()
Next
End Sub
Sub SaveValuesToFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Writeline()
Next
End Sub
'and more similar functions/subs
As you can see, what this class does is that it takes lot of different variables from different places into a collection, and then various functions read or write values to every variable in that collection using loops. In this example, I have just a few variables, but most of the time there are dozens.
Reading the values is not a problem. Writing them, is, because when I declare that VariableList at the top of my class, that List just makes a copy of each variable, rather than maintaining a reference to it. Meaning that if, say, one of the functions modifies the MainForm.X in that List, the actual variable MainForm.X is not modified. To work with references, I would have to forgo loops, and assign every single variable manually, in every function. Which is obviously a lot of bad code. I want to declare that list of variables only once, and then use loops, like in this example code that I wrote above. My question is, how can I make such a container (List, Array, whatever) that would retain the references to the original variables in it, and make the code above possible?

There is no easy way to store pointers to variables in VB.NET. As a workaround, you can use a class to store your variables, as a class is always used as a pointer.
Here's an example of a way to achieve this with a ContainerClass which own a Dictionary of integers. One interest of this method would be that you can declare and name "variables" dynamically. In reality, they will be managed KeyValuePair. Once you have instantiated a copy of this class, you can use it to "manage" your variables by using this class as your pointer.
I included a loop which set all the integers to the same number just for fun, and to demonstrate the kind of manipulation which would end up having an effect similar to one of those described in your question.
Public Class Form2
'This is the container class which will be used to bypass the lack of pointers
'if you wanted to change a property, like the window width, it would be more difficult, but simples variables will be no trouble
Private variableContainer As New VariableContainer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
variableContainer.AddVar("X", 5)
variableContainer.AddVar("Y", 15)
Debug.Print(variableContainer.GetVar("X"))
Debug.Print(variableContainer.GetVar("Y"))
variableContainer.SetAllVar(42)
Debug.Print("Next line will print 42")
Debug.Print(variableContainer.GetVar("X"))
End Sub
End Class
Public Class VariableContainer
'I know a public variable wouldn't need the fancy functions down there, but it's usually better to encapsulate, especially if you're working with a team
'and "future you" count as a teammate, never forget that...
Private list As New Dictionary(Of String, Integer)
Public Sub AddVar(ByVal name As String, ByVal value As Integer)
list.Add(name, value)
End Sub
Public Function GetVar(ByVal name As String) As Integer
If list.ContainsKey(name) Then
Return list(name)
Else
'I choose -1 arbitrarily, don't put too much thinking into this detail
Return -1
End If
End Function
Public Sub SetVar(ByVal name As String, ByVal num As Integer)
If list.ContainsKey(name) Then
list(name) = num
End If
End Sub
Public Sub SetAllVar(ByVal num As Integer)
Dim dict As New Dictionary(Of String, Integer)
For Each item As KeyValuePair(Of String, Integer) In list
dict.Add(item.Key, num)
Next
list = dict
End Sub
End Class
Have fun!

Related

Recursive value in a function

I am trying to develop a simple class-based function that will modify a previous value determined by the function, that is, it is a recurrence relationship.
In essence, I am developing my own random number generator which will work the same way the current Random class works, i.e.
Dim ran as New Random(123456)
For i = 0 To 9
MessageBox.Show(ran.NextDouble & " " & ran.Next(1,11))
Next
I can successfully do this using a class-based method simply by sending a value ByRef, but as you know for a method call, the old value to be modified needs to be placed inside the call to the method. Thus, I am trying to overcome use of a method or a global typed variable, and rather would like the instantiated class to somehow remember what the current value is.
The example code below attempts to multiply the value _value by 2 during every function call, so the expected result would be 2, 4, 8, 16, etc. However, even though a 2 is initially sent to the constructor, the value of _value is always returned as zero.
Class Example
Public _value As Integer
Public Sub New(ByVal _value)
End Sub
Public Function Value() As Integer
_value *= 2
End Function
End Class
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim x As New Example(2)
For i = 0 To 9
MessageBox.Show(x.Value)
Next
End Sub
Normally fields are Private. If you want to expose data from your class you would use a Public Property.
Change the name of the parameter for Sub New. If properly qualified your name will work but it is confusing. You must do something with the passed in value! Assign it to your field _value.
Your Function has no return value. It simply changes the value of _value. If you don't return anything use a Sub. Change the name of your Function to something meaningful. Add a Return statement to send a value back to the calling code.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim x As New Example(2)
For i = 0 To 9
MessageBox.Show(x.DoubleValue.ToString)
Next
End Sub
Class Example
Private _value As Integer
Public Sub New(ByVal Input As Integer)
_value = Input
End Sub
Public Function DoubleValue() As Integer
_value *= 2
Return _value
End Function
End Class

ByVal in Excel event-handler

I am a little confused about the usage of ByVal keyword in some event-handlers in Excel.
The NewSheet event example
(Copied from Excel 2019 Power Programming with VBA, Wiley)
Private Sub Workbook_NewSheet(ByVal Sh As Object)
If TypeName(Sh) = "Worksheet" Then
Sh.Cells.ColumnWidth = 35
Sh.Range("A1") = "Sheet added " & Now()
End If
End Sub
This code works as expected, as seems trivial. But after some thought I am getting more and more confused.
Based on my understanding ByVal means Sh is just copy of the the original worksheet object, and procedures using ByVal arguments won't result in change to the original object. In other words, what the code does should have no effect.
Only when references are passed to procedures will they be able to modify objects referred to by those references.
Am I missing something there? Thanks
P.S. Most of my understanding of passing by reference/value comes from other programming languages such as C#. There might be some peculiarity in VBA I am not aware of.
Since you're passing an Object, ByVal passes a copy (the value) of the reference. The copy (value) of the reference still points to what the original reference is pointing to. Hence your method works and changes what the original reference is pointing to.
If you know Java, this is similar in nature to Java, where everything can be said to 'pass by value' but when you pass an Object as a parameter, what you are actually doing is passing a copy of the reference, and not a copy of what the reference is pointing to.
ByVal in those cases prevents the change of the value in the original reference itself.
Please see next:
Module Module1
Sub Main()
' Declare an instance of the class and assign a value to its field.
Dim c1 As New Class1()
c1.Field = 5
Console.WriteLine(c1.Field)
' Output: 5
' ByVal does not prevent changing the value of a field or property.
ChangeFieldValue(c1)
Console.WriteLine(c1.Field)
' Output: 500
' ByVal does prevent changing the value of c1 itself.
ChangeClassReference(c1)
Console.WriteLine(c1.Field)
' Output: 500
Console.ReadKey()
End Sub
Public Sub ChangeFieldValue(ByVal cls As Class1)
cls.Field = 500
End Sub
Public Sub ChangeClassReference(ByVal cls As Class1)
cls = New Class1()
cls.Field = 1000
End Sub
Public Class Class1
Public Field As Integer
End Class
End Module

VBA Encryption Method

I have written a very simple Ceaser Cipher encryption algorithm on VBA. It takes a string value and applies x shift.
Quite happy with it. However, would like to take it to the next step, but I am not sure if it is doable.
I would like to call this encryption function in another module and pass a string value to encrypt.
I.e. What I am trying to do is something like this
Private Sub Encryption()
'Encryption method of string Var
End Sub
---- and in another sub ----
Private Sub function()
Dim Text as String
Text = "Hello"
Encryption(Text)
End Sub
So in this example, I have defined a method to encrypt any string variable. In the other function, I defined a string and initialised it as "Hello". Then called Encryption function on it... Not sure if this is doable in VBA?
I am not sure how I could pass in a variable when I am calling a function within a function. Any advise please?
You would need to change the Sub Routine to a Function. Also, get rid of the Private keyword as this will not allow you to call it across different modules.
So Change this:
Private Sub Encryption()
'Encryption method of string Var
End Sub
to this:
Function Encryption(ByVal inputString as String) As String
'Encryption method of string Var
End Sub
Now, inside your function you need to change whatever variable holds the first string (the one to be encrypted) to the newly created variable, inputString. It's hard to help you in this area as you did not provide the full code for Sub Encryption().
Also, ensure that you set the Function name itself to the newly encrypted text within the function. So your function would essentially look as follows:
Function Encryption(ByVal inputString as String) As String
'Encryption method of string Var
Encryption = 'the value to return from this function
End Sub
Now, your second sub would look like this:
Private Sub test()
Dim Text as String, encryptedText as String
Text = "Hello"
encryptedText = Encryption(Text)
End Sub
You will just make the new variable encryptedText equal to the new function, Encryption.
So, the major difference between a Sub and a Function is that a Function will return values. They both essentially process code the same way.
METHOD 2 (Edit)
After rethinking your question, I believe that you were using your Sub to obtain your encrypted text from passing the variable as ByRef, and you may just be having difficulties with calling the Sub because of the Private keyword.
If this what was going on, then you can simply remove the Private keyword and it should work as intended.
So your Sub should look like:
Sub Encryption() '<-- Notice, no Private scope
'Encryption method of string Var
End Sub
or
Public Sub Encryption()
'Encryption method of string Var
End Sub
You should take a look at this for additional reading regarding scope.

How can I sort some objects depending on one of their properties

I want to sort a number of objects in certain order (ascending or descending) depending on one of their properties. I learned that interfaces can help doing this but can't figure out how to do this.
I am going to figure out what I want to do
I'll try to shorten my code to only touch the problem
Public class Course
Public property priority as integer
Public property code as string
Public sub new (byval a as integer,
byval b as string)
End sub
End class
Module module1
Public sub main ()
Dim a as new course(3,"ECE333")
Dim b as new course (5,"ECE332")
Dim c() = {a,b}
End sub
End module
So I want to sort the course objects at c descending order according to their priority
This is the best I can do for you until we have more information about the items in your question:
Dim sorted = items.OrderBy(Function(i) i.Property)
Use the List.sort method. You will have to provide a Comparator method to do the comparison.
Say, if your list is called myList and it has a property called height and you want to sort by height.
You can do the following:
myList.Sort(Function(a, b) a.height.CompareTo(b.height))

Vb.net Custom Class Property to lower case

I am trying to create a settings class.
The Property Test() is a list of strings.
When I add a string such as: t.test.Add("asasasAAAAA")
I want it to autmatically turn lowercase.
For some reason it is not. Any Ideas?
p.s.
using t.test.Add(("asasasAAAAA").ToLower) will not work for what I need.
Thank you.
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim t As New Settings
t.test.Add("asasasAAAAA")
t.test.Add("aBBBBBAAAAA")
t.test.Add("CCCCCsasAAAAA")
End Sub
End Class
Public Class Settings
Private strtest As New List(Of String)
Public Property test() As List(Of String)
Get
Return strtest
End Get
Set(ByVal value As List(Of String))
For i As Integer = 0 To value.Count - 1
value(i) = value(i).ToLower
Next
strtest = value
End Set
End Property
End Class
ashakjs
That's the reason: set accessor of your property is actually never called.
when you use t.test.Add("asasasAAAAA") you are actually calling a get accessor, which returns a list, after that specified string is added to this list, so .ToLower function is never called.
Simple way to fix this:
Dim list as New List(Of String)
list.Add("asasasAAAAA")
list.Add("aBBBBBAAAAA")
list.Add("CCCCCsasAAAAA")
t.test = list
Alternatively, you can implement your own string list (easiest way - inherit from Collection (Of String)), which will automatically convert all added string to lower case.
What you are trying to do and what you are doing don't match. To do what you want, you need to create your own collection class extending the generic collection - or provide a custom method on your settings class which manually adjusts the provided string before adding it to the local (private) string collection.
For an example of the second option, remove the public property of the settings class which exposes the list of string and use a method like the following:
Public Sub Add(ByVal newProp As String)
strtest.Add(newProp.toLower())
End Sub