vba public variables with changing months - vba

I'm a begginer at VBA so i have no idea what's is wrong. My point is to name some sheets but i thought i could dim them as string. I made public variable but vba dosent see it. Could somebode tell what is excatly wrong?
Public Sub VariableMonths()
Public one, two, three, DeleteIt As String
one = "M_11_17"
two = "M_10_17"
three = "M_09_17"
DeleteIt = "M_08_17"
End Sub
then some part after this public sub - no variable is found
Sheets(two).Copy Before:=Sheets(4)
Sheets(two & "( 2)").Select
Sheets(two & "( 2)").Move Before:=Sheets(two)
Sheets(two & "( 2)").Select
Sheets(two & "( 2)").Name = one
Sheets(two).Select
Cells.Select
Selection.Copy

In a standard Module:
Dim one As String, two As String, three As String, DeleteIt As String
Sub setupVariables()
one = "M_11_17"
two = "M_10_17"
three = "M_09_17"
DeleteIt = "M_08_17"
End Sub
Sub MAIN()
Call setupVariables
MsgBox three
End Sub

Related

How to handle a function where the returned value type is not known at run-time (Object or Non-Object)

This question centers around the return value of a call to CallByName. I have a class called PropertyPtr which is meant to act as a generic pointer to an object property. It holds a reference to an Object, and the name of one of its properties. It exposes a Getter and Setter method.
PropertyPtr:
Option Explicit
Public Obj As Object
Public PropertyName As String
Public Sub Setter(Val As Variant)
If IsObject(Val) Then
CallByName Me.Obj, Me.PropertyName, VbSet, Val
Else
CallByName Me.Obj, Me.PropertyName, VbLet, Val
End If
End Sub
Public Function Getter() As Variant
If IsObject(CallByName(Me.Obj, Me.PropertyName, VbGet)) Then
Set Getter = CallByName(Me.Obj, Me.PropertyName, VbGet)
Else
Getter = CallByName(Me.Obj, Me.PropertyName, VbGet)
End If
End Function
In the Getter, my CallByName could return a object or not. But the only way I can see to test if the CallByName value will be an object is to end up running it twice - once to test inside an IsObject and then again to get a reference to the value. The only other way I could see doing this is trapping for an error. Then, you at least only SOMETIMES run the CallByName twice.
My question is: is there some other way to do this without running CallByName twice?
Okay, so if you really want to follow that route then I think you'll have to set an IsObj flag - probably at the point you set the property name.
However, I'd still maintain that using a Variant for either an Object or primitive type isn't a great idea, and the CallByName() function in this context comes with issues. My hesitations are that performance will be diminished and you'll have quite a task to keep the property strings aligned with the property names (should you update things in the future).
It is possible to implement a Mediator Pattern in VBA and I do feel you should consider this route. Below is a really basic example of how you could do it. I haven't bothered with an interface for the mediator, but I have created an interface for my participating classes (to cover the possibility that you're dealing with your own 'groups' of classes).
Mediator class (called cMediator):
Option Explicit
Private mSweets As Collection
Private Sub Class_Initialize()
Set mSweets = New Collection
End Sub
Public Sub RegisterSweet(sweet As ISweet)
Set sweet.Mediator = Me
mSweets.Add sweet
End Sub
Public Sub SendSugarLimit(limit As Long)
Dim sweet As ISweet
For Each sweet In mSweets
sweet.ReceiveSugarLimit limit
Next
End Sub
Public Sub ReceiveMeltingAlert(offender As String)
Dim sweet As ISweet
For Each sweet In mSweets
sweet.ReceiveEatNow offender
Next
End Sub
Participating classes Interface (called ISweet):
Option Explicit
Public Property Set Mediator(RHS As cMediator)
End Property
Public Sub ReceiveSugarLimit(g_perDay As Long)
End Sub
Public Sub ReceiveEatNow(offender As String)
End Sub
My two participating classes (cQtySweet and cWeightSweet):
Option Explicit
Implements ISweet
Public Name As String
Public SugarPerItem As Long
Public CanMelt As Boolean
Private pMediator As cMediator
Public Sub OhNoItsMelting()
pMediator.ReceiveMeltingAlert Name
End Sub
Private Property Set ISweet_Mediator(RHS As cMediator)
Set pMediator = RHS
End Property
Private Sub ISweet_ReceiveEatNow(offender As String)
If CanMelt Then Debug.Print offender & " is melting. Eat " & Name & "s now!"
End Sub
Private Sub ISweet_ReceiveSugarLimit(g_perDay As Long)
Dim max As Long
max = g_perDay / SugarPerItem
Debug.Print "Max " & Name & "s: " & max & "."
End Sub
Option Explicit
Implements ISweet
Public Name As String
Public SugarPer100g As Long
Public CanMelt As Boolean
Private pMediator As cMediator
Public Sub OhNoItsMelting()
pMediator.ReceiveMeltingAlert Name
End Sub
Private Property Set ISweet_Mediator(RHS As cMediator)
Set pMediator = RHS
End Property
Private Sub ISweet_ReceiveEatNow(offender As String)
If CanMelt Then Debug.Print offender & " is melting. Eat " & Name & " now!"
End Sub
Private Sub ISweet_ReceiveSugarLimit(g_perDay As Long)
Dim max As Long
max = g_perDay / (SugarPer100g / 100)
Debug.Print "Max " & Name & ": " & max & "g."
End Sub
Module Code:
Public Sub RunMe()
Dim m As cMediator
Dim qtySweet As cQtySweet
Dim weightSweet As cWeightSweet
Set m = New cMediator
Set qtySweet = New cQtySweet
With qtySweet
.Name = "Gobstopper"
.SugarPerItem = 5
.CanMelt = False
End With
m.RegisterSweet qtySweet
Set qtySweet = New cQtySweet
With qtySweet
.Name = "Wine Gum"
.SugarPerItem = 2
.CanMelt = True
End With
m.RegisterSweet qtySweet
Set weightSweet = New cWeightSweet
With weightSweet
.Name = "Sherbert"
.SugarPer100g = 80
.CanMelt = False
End With
m.RegisterSweet weightSweet
Set weightSweet = New cWeightSweet
With weightSweet
.Name = "Fudge"
.SugarPer100g = 50
.CanMelt = True
End With
m.RegisterSweet weightSweet
'Blasted government has reduced sugar allowance.
Debug.Print "New govt. limits..."
m.SendSugarLimit 200
'Phew what a scorcher - the fudge is melting in my pocket.
Debug.Print "Sweet alarm..."
weightSweet.OhNoItsMelting
End Sub
… and the output looks like this:
New govt. limits...
Max Gobstoppers: 40.
Max Wine Gums: 100.
Max Sherbert: 250g.
Max Fudge: 400g.
Sweet alarm...
Fudge is melting. Eat Wine Gums now!
Fudge is melting. Eat Fudge now!

Calling a function using variable name in VBA

Thanks in advance.
I'm trying to call a function using a variable name. It's throwing the error
The macro 'testfunc' cannot be found.
Resume without error
My Code: I tried below 3 statements but all are throwing the same error.
sFuncName = "testfunc"
ExtractData = Application.Run("Sheet.xls" & sFuncName, s1, s2)
ExtractData = Application.Run(sFuncName, s1, s2)
ExtractData = CallByName("Sheet.xls", sFuncName, VbMethod, s1, s2)
Note that the sFuncName returns a value & I have 2 Excels opened at that moment, I'm trying to copy data from one Excel to other.
Please help me in achieving this.
Private Function ABC
sFuncName = oMapWksht.Cells(sMapRowItr, 3).Value
'sFuncName = "testfunc"
ExtractData = Application.Run("Sheet.xls" & sFuncName, s1, s2)
'ExtractData = Application.Run(sFuncName, s1, s2)
'ExtractData = CallByName("Sheet.xls", sFuncName, VbMethod, s1, s2)
End Function
Public Function testfunc(ByVal s1 As String, ByVal s2 As String) As Boolean
--
---
----
End Function
If the function you're calling is in the same workbook as the caller, it doesn't matter how many workbooks are opened.
If the invoked procedure doesn't have a unique name across the entire project, you need to qualify the function name with at least the name of the module though. This should get you going:
Module1 (caller)
Sub DoSomething()
Debug.Print Application.Run("Module2.Test", "Test")
End Sub
Module2 (callee)
Public Function Test(ByVal title As String) As VbMsgBoxResult
Test = MsgBox("Works?", vbQuestion + vbYesNo, title)
End Function
You can use a String variable to hold the Sub's name. Here is an example of a Sub in the same module as the caller:
Sub MAIN()
Dim st As String
st = "whatever"
Application.Run st
End Sub
Public Sub whatever()
MsgBox "whatever"
End Sub
If the caller and called subs are in different modules, the syntax may be slightly different.
EDIT#1:
If we want to call a Sub in a different module within the same workbook, the syntax would be the same.
If we want to call a Sub within another workbook, and both workbooks are open then the caller would look like:
Sub MAIN()
Dim st As String
st = "Book2.xlsm!whatever"
Application.Run st
End Sub
I usually declare the called Sub Public, but I don't know if this is necessary.
EDIT#2
To use another workbook's UDF() in a worksheet cell, then:
where qwerty() is a Public UDF()

VB: Referencing a variable by name in a conditional

I am an amateur programmer and in working with a friend and cannot find a solution to our coding dilemma.
We need to be able to compare if a Variable matches data found in second variable, but the first Variable to be searched is to be dependent on the contents of a third variable. (The third variable would name the first variable to be searched)
Var1, Var2, Var3 ... Var100 'Each with their own values and datatypes;
Var45 = 25
Vartocheck1 = "Var45"
Vattocheck2 = 25
If Vartocheck1 = Vartocheck2 Then
(Stuff)
End If
Essentially, I was wondering if there was a good way to compare two variables, most likely in an If-then statement, where one of the two variables is decided by a third variable's contents.
The idea is Vartocheck1 would be a string, containing the NAME of the variable whose value I want to check against Vartocheck2. The issue is that the variables in the code (in my example: Var1, Var2, Var3 ... Var100) are defined as the process runs, but an external excel chart, when referenced, can change certain variables during the program's execution. I could accomplish what I need with about a million nested if-then statements, but that is slow and messy, and I am hoping there is another way.
I have looked into arrays, but implementing the massive number and size of the arrays would be daunting and require an entire project rewrite.
Is there any good method for comparing a variable like this?
What you are looking for is a concept called reflection. This SO question explains it:
How to get the variable names types, and values in the current class or Method in VB.net?
Based on that I have quickly created the following class:
Public Class Class1
Public This As String
Public That As Boolean
Public Function ListVar() As Boolean
Dim fields As System.Reflection.FieldInfo() = Me.GetType().GetFields()
For Each fld As System.Reflection.FieldInfo In fields
Dim name As String = fld.Name
Dim value = fld.GetValue(Me)
Dim typ As Type = fld.FieldType
Debug.Print(name)
Next
Return True
End Function
End Class
You can call the ListVar function from anywhere by doing this:
Dim c As New Class1
c.ListVar()
Obviously this is not production ready, but should give you a start.
While I still think this is an XY problem, one convenient container in .net is a dictionary. This allows you to store key-value pairs which can be of any type. This gives you some of the tools you would get with a database (which may be a better solution in this case). For example :
Imports System.Collections.Generic
Module Module1
Dim ValueDict As New Dictionary(Of String, Integer)
Sub Main()
Dim r As New Random
'Fill the dictionary with keys "Var1" -> "Var100"
'Fill the values with random integers
For i As Integer = 1 To 100
ValueDict.Add("Var" & i.ToString, r.Next)
Next
'Extract a variable by name
Dim extractedVar As Integer
If ValueDict.TryGetValue("Var23", extractedVar) Then
Console.WriteLine("Var23 has value :" & extractedVar.ToString())
Else
Console.WriteLine("Var23 does not exist in the dictionary")
End If
'enumerate all values
For Each valuePair As KeyValuePair(Of String, Integer) In ValueDict
Console.WriteLine("Variable " & valuePair.Key & _
" = " & valuePair.Value.ToString())
Next
'Get a variable by number
Dim varNumber As Integer = 72
If ValueDict.TryGetValue("Var" & varNumber.ToString(), extractedVar) Then
Console.WriteLine("Var" & varNumber.ToString() & _
" has value :" & extractedVar.ToString())
Else
Console.WriteLine("Var" & varNumber.ToString() & _
" does not exist in the dictionary")
End If
Console.ReadLine()
End Sub
End Module
Other types of operations :
'Check if value exists, Assign a new value or update an existing value
Dim newVal As Integer = 12345
Dim varName As String = "Var147"
If Not ValueDict.ContainsKey(varName) Then
Console.WriteLine(varName & " does not currently exist")
End If
ValueDict.Item(varName) = newVal
Console.WriteLine(varName & " now has value :" & ValueDict.Item(varName).ToString())
'Delete a value
ValueDict.Remove(varName)
If Not ValueDict.ContainsKey(varName) Then
Console.WriteLine(varName & " does not currently exist")
End If

Threading Problems (I don't understand it)

There's lots and lots of pages on the internet regarding threading but I can't seem to get my head around it.
I have a Form, which on the click of a button, loops through a file and reads it line by line. Each line is the login details for different FTP sites.
When it reads a line, it Dim's a variable as a new instance of a class named CallFTP using the login details.
It then Dim's a variable as a new Thread using a function in CallFTP named PerformFTP.
PerformFTP returns a string with the results of the FTP and I want to add this to a ListBox on the form that began it all.
The code for the button goes like this...
Private Sub cmdRun_Click(sender As Object, e As EventArgs) Handles cmdRun.Click
For Each _FTPLine As String In Split(_FTPDetails, vbNewLine)
Dim _Active As Boolean = CBool(Split(_FTPLine, "|")(7))
If _Active Then
_CurNum += 1
_ID = Format(Now.Year, "0000") & Format(Now.Month, "00") & Format(Now.Day, "00") & Format(Now.Hour, "00") & Format(Now.Minute, "00") & Format(Now.Second, "00") & Format(Now.Millisecond, "000") & Format(_CurNum, "00000")
Dim _FTP As New CallFTP(_ID, Split(_FTPLine, "|")(0), Split(_FTPLine, "|")(1), Split(_FTPLine, "|")(2), Split(_FTPLine, "|")(3), Split(_FTPLine, "|")(4), Split(_FTPLine, "|")(5), Split(_FTPLine, "|")(6))
Dim _Thread = New Thread(New ThreadStart(AddressOf _FTP.PerformFTP))
With _Thread
.IsBackground = True
.Start()
End With
End If
Next _FTPLine
End Sub
The class is as below (not quite but you don't need the rest of the code lol)
Public Class CallFTP
Private _ID As String = ""
Private _Response As String = ""
Private _IPAddress As String = ""
Private _Port As String = ""
Private _User As String = ""
Private _Pass As String = ""
Private _Remote As String = ""
Private _Local As String = ""
Private _InOut As String = ""
Public Sub New(ID As String, Server As String, PortNum As String, Username As String, Password As String, RemoteDir As String, LocalDir As String, InOrOut As String)
_ID = ID
_IPAddress = Server
_Port = PortNum
_User = Username
_Pass = Password
_Remote = RemoteDir
_Local = LocalDir
_InOut = InOrOut
End Sub
Public Function PerformFTP() As String
Return "This is a test"
End Function
End Class
Could anyone explain how I would call a sub named LogMessage on a module named modMisc (which adds a string to a ListBox on the main form)?
I've read that you need to invoke it but everything I read seems to give me a headache and make me need to lie down in a dark room for a few hours.
Is anyone capable of explaining as though you're speaking to a 2 year old? :)
Any help would be much appreciated.
You need to invoke a delegate to update your GUI if you're going to update it from another thread that from where it was created.
1º Your delegate must match (have the same signature) than the method you'll use:
Delegate Sub LogMessageExampleDelegate(ByVal x As Integer, ...)
Signature means that the delegate must return and receive the same types than your function/method.
2º Call your function to update GUI using delegate. This for example inside your update GUI function:
If yourListBox.InvokeRequired Then
yourListBox.Invoke(New LogMessageExampleDelegate(AddressOf THE_FUNCTION_WHICH_UPDATES_THE_GUI_NAME), parameter_value)
Else
'Just call your function
End If
With, as example:
sub addToListBox(byval text as string)
myListBox.Items.add(text)
end sub
So your invoke would be:
If yourListBox.InvokeRequired Then
yourListBox.Invoke(New LogMessageExampleDelegate(AddressOf addToListBox), "Item 1")
Else
'Just call your function
addToListBox("Item 1")
End If
PS: I wrote it two times so hope I didn't mess up with something without noticing it.

Access variables of sub in VBA

I have the following sub
Public Static Sub Varib()
Device_ = Sheet1.DeviceType_.Text
Model_ = Sheet1.Model_.Text
Security_ = Sheet1.SecurityGroup_.Text
Catagory_ = Application.Index(Worksheets("Temp_for_varible_lists").Range("b:b"), Application.Match(x, Worksheets("Temp_for_varible_lists").Range("A:A"), 0))
End Sub
It in fact carries on and in total produces a whole bunch of vaules of various datatypes based on the users input.
So the user choses from a few check boxes, list boxes, fills in some text boxes and hits a submit button and this sub populates a number of varibles from that, that are then uterlised by other funcation and sub in the application.
Now I could make all the varibles Global and access them in that fassion. But I was hoping for something more like what I have seen with c# and VB.net
where you can get the value by using
sub.varible name
example for the code above.
Sub Main()
x = Varib.Device_
msgbox(x)
end sub
is there a simmular way to do this in VBA?
Cheers
aaron
What you're asking cannot be done. The solution is not to make your variables global either (generally a bad idea, with some exceptions, this case not being one of them).
One possibility is to create a user-defined type:
Type Varib
Device_ As String
Model_ As String
Security_ As String
Category_ As String
End Type
and a sub to populate it from your sheet:
Sub LoadVaribFromSheet(v As Varib)
With v
.Device_ = Sheet1.DeviceType_.Text
.Model_ = Sheet1.Model_.Text
.Security_ = Sheet1.SecurityGroup_.Text
.Category_ = _
Application.Index(Worksheets("Temp_for_varible_lists").Range("b:b"), _
Application.Match(x, _
Worksheets("Temp_for_varible_lists").Range("A:A"), 0))
End With
End Sub
You can then use this as follows:
Sub Main()
Dim myVarib As Varib
LoadVaribFromSheet myVarib
' Now do stuff with myVarib ...
MsgBox myVarib.Device_
End Sub
you can use encapsulation for this
Private value As String
Private value1 As String
Public Function setValue(val As String)
value = val
End Function
Public Function setValue1(val As String)
value1 = val
End Function
Public Function getValue() As String
getValue = value
End Function
Public Function getValue1() As String
getValue1 = value1
End Function
-------------------------------------------------------------------------
Sub test()
MsgBox getValue & vbCrLf & getValue1
setValue "myValue"
setValue1 "myValue1"
MsgBox getValue & vbCrLf & getValue1
End Sub