Very basic using of VBA property - vba

I would like to use this property:
Public Property Get HasNoData() As Boolean
HasNoData = (numberOfColumns < 2 And numberOfRows < 2)
End Property
Sub test()
Dim numberOfColumns As Long
Dim numberOfRows As Long
numberOfColumns = 5
numberOfRows = 3
If HasNoData Then
MsgBox True
Else:
MsgBox False
End If
End Sub
Everytime I get TRUE, no matter whether the conditions are met or not. I might get the whole idea wrong, so please let me know.

There are a few things that are wrong with your code as it stands now:
Properties can only be members of a class
Variables are subject to what is known as scope, and specifically your two variables numberOfColumns and numberOfRows are only available within the scope of the test() Sub, so they and their values cannot be seen inside the HasNoData() property unless they are passed as parameters or set as members of the class
The VBA Msgbox takes a String as the parameter, whereas you have provided a Boolean
I think what you really want is to make "HasNoData" into a function that takes two parameters, one for rows and one for columns. Try this:
Public Function HasNoData(NumRows, NumCols) As Boolean
HasNoData = (NumCols < 2 And NumRows < 2)
End Function
Sub test()
If HasNoData(3, 5) Then
MsgBox "There is data!"
Else
MsgBox "There is no data."
End If
End Sub

Related

Can-I assign a value directly to an object?

In VBA, you can use either Cells or Cells.Value, it has the same effect. Test1 and Test2 behaves the same way, allthough in test 2 the string is passed directly to the object.
Sub Test1()
Cells(1, 1) .Value = "Hello"
End Sub
Sub Test2()
Cells(1, 2) = "World"
End Sub
Is it possible to do something similar with any user class? Can-I assign a value directly to an object created from one of my classes withpout using the property value ?
Following Tim and K. recommendations, I've created a the following class:
Option Explicit
Dim LNG_Debut As Long
Public Property Let Debut(tLNG_Debut As Long)
LNG_Debut = tLNG_Debut
End Property
Property Get Debut() As Long
Debut = LNG_Debut
End Property
Then, I’ve exported that class to notepad and modified it the following way:
Property Get Debut() As Long
Attribute Debut.VB_UserMemId = 0
Debut = LNG_Debut
End Property
And finaly, I’ve imported it back in the VBA editor.
Then, both Test1 and Test2 have the same result
Sub Test1()
Dim MyVariable As obj_Test
Set MyVariable = New obj_Test
MyVariable.Debut = 10
End Sub
And
Sub Test2()
Dim MyVariable As obj_Test
Set MyVariable = New obj_Test
MyVariable = 10
End Sub
Many thanks

VBA Object module must Implement ~?

I have created two classes, one being an interface for the other. Each time I try to instantiate Transition_Model I get:
Compile error: Object Module needs to implement '~' for interface'~'
To my understanding Implementing class is supposed to have a copy of all public subs, function, & properties. So I don't understant what is the problem here?
Have seen similar questions come up but either they refer to actual Sub or they include other complications making answer too complicated for me to understand.
Also note I tried changing Subs of Transition_Model to Private and add 'IModel_' in front of sub names(Just like top answer in second question I linked) but I still receive the same error.
IModel
Option Explicit
Public Enum Model_Types
Transition
Dummy
End Enum
Property Get M_Type() As Model_Types
End Property
Sub Run(Collat As Collateral)
End Sub
Sub Set_Params(key As String, value As Variant)
End Sub
Transition_Model
Option Explicit
Implements IModel
Private Transitions As Collection
Private Loan_States As Integer
Private Sub Class_Initialize()
Set Transitions = New Collection
End Sub
Public Property Get M_Type() As Model_Types
M_Type = Transition
End Property
Public Sub Run(Collat As Collateral)
Dim A_Transition As Transition
Dim New_Balance() As Double
Dim Row As Integer
For Row = 1 To UBound(Collat.Curr_Balance)
For Each A_Transition In Transitions
If A_Transition.Begining = i Then
New_Balance = New_Balance + Collat.Curr_Balance(Row) * A_Transition.Probability
End If
Next A_Transition
Next
End Sub
Public Sub Set_Params(key As String, value As Double)
Dim Split_key(1 To 2) As String
Dim New_Transition As Transition
Split_key = Split(key, "->")
Set New_Transition = New Transition
With New_Transition
.Begining = Split_key(1)
.Ending = Split_key(2)
.Probability = value
End With
Transitions.Add New_Transition, key
End Sub
Lastly the Sub I am using to test my class
Sub Transition_Model()
Dim Tested_Class As New Transition_Model
Dim Collat As New Collateral
'Test is the model type is correct
Debug.Assert Tested_Class.M_Type = Transition
'Test if Model without transition indeed does not affect balances of its collateral
Collat.Curr_Balance(1) = 0.5
Collat.Curr_Balance(2) = 0.5
Tested_Class.Run (Collat)
Debug.Assert ( _
Collat.Curr_Balance(1) = 0.5 And _
Collat.Curr_Balance(2) = 0.5)
End Sub
Actaully Per the second question I linked has the correct answer which I missed.
All subs need to start with 'IModel_' and rest ot the name has to match the name in IModel.
AND
This is the part i missed, you cannot use underscore in the Sub name.

VBA calling class let method, compile error

Beginner to excel class modules here. I am having trouble with the basics-
When I set (let) the property, I get "Compile error: Wrong number of arguments or invalid property assessment" with the .Name property:
Sub test()
Dim acc As account
Set acc = New account
MsgBox (acc.Name("First Account").rowNum())
End Sub
And this is the "account" class module:
Private strAccName As String
Private mlngRowNum As Long
Public Property Let Name(strN As String)
strAccName = strN
End Property
Public Property Get rowNum(exists As Boolean)
dim rowNum as Long
'...some logic here...
'...
getRowNum = rowNum
End Property
So supposedly I am going wrong in the Let method? Advice greatly appreciated
you can assign a value to a property LET (for normal dataTypes) or property SET (for Object) by the equal sign, not vith parenthesis (used for method instead), or read a property GET assigning the value to another variable, like this:
acc.Name = "xyz"
MsgBox acc.Name
This might help you:
Sub test_class()
Dim acc As account
Set acc = New account
acc.Name = "First Account"
MsgBox acc.rowNum(1)
End Sub
class (account):
Private strAccName As String
Private mlngRowNum As Long
Public Property Let Name(strN As String)
strAccName = strN
End Property
Public Property Get rowNum(exists As Boolean)
'Dim rowNum As Long
'...some logic here...
'...
If exists Then
'getRowNum = rowNum
rowNum = 5
Else
rowNum = 10
End If
End Property

Checking if a value is a member of a list

I have to check a piece of user input against a list of items; if the input is in the list of items, then direct the flow one way. If not, direct the flow to another.
This list is NOT visible on the worksheet itself; it has to be obfuscated under code.
I have thought of two strategies to do this:
Declare as an enum and check if input is part of this enum, although I'm not sure on the syntax for this - do I need to initialise the enum every time I want to use it?
Declare as an array and check if input is part of this array.
I was wondering for VBA which is better in terms of efficiency and readability?
You can run a simple array test as below where you add the words to a single list:
Sub Main1()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test("dog") 'True
Debug.Print "horse", Test("horse") 'False
End Sub
Function Test(strIn As String) As Boolean
Test = Not (IsError(Application.Match(strIn, arrList, 0)))
End Function
Or if you wanted to do a more detailed search and return a list of sub-string matches for further work then use Filter. This code would return the following via vFilter if looking up dog
dog, dogfish
In this particular case the code then checks for an exact match for dog.
Sub Main2()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test1("dog")
Debug.Print "horse", Test1("horse")
End Sub
Function Test1(strIn As String) As Boolean
Dim vFilter
Dim lngCnt As Long
vFilter = Filter(arrList, strIn, True)
For lngCnt = 0 To UBound(vFilter)
If vFilter(lngCnt) = strIn Then
Test1 = True
Exit For
End If
Next
End Function
Unlike in .NET languages VBA does not expose Enum as text. It strictly is a number and there is no .ToString() method that would expose the name of the Enum. It's possible to create your own ToString() method and return a String representation of an enum. It's also possible to enumerate an Enum type. Although all is achievable I wouldn't recommend doing it this way as things are overcomplicated for such a single task.
How about you create a Dictionary collection of the items and simply use Exist method and some sort of error handling (or simple if/else statements) to check whether whatever user inputs in the input box exists in your list.
For instance:
Sub Main()
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
myList.Add "item1", 1
myList.Add "item2", 2
myList.Add "item3", 3
Dim userInput As String
userInput = InputBox("Type something:")
If myList.Exists(userInput) Then
MsgBox userInput & " exists in the list"
Else
MsgBox userInput & " does not exist in the list"
End If
End Sub
Note: If you add references to Microsoft Scripting Runtime library you then will be able to use the intelli-sense with the myList object as it would have been early bound replacing
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
with
Dim myList as Dictionary
Set myList = new Dictionary
It's up to you which way you want to go about this and what is more convenient. Note that you don't need to add references if you go with the Late Binding while references are required if you want Early Binding with the intelli-sense.
Just for the sake of readers to be able to visualize the version using Enum let me demonstrate how this mechanism could possibly work
Enum EList
item1
item2
item3
[_Min] = item1
[_Max] = item3
End Enum
Function ToString(eItem As EList) As String
Select Case eItem
Case EList.item1
ToString = "item1"
Case EList.item2
ToString = "item2"
Case EList.item3
ToString = "item3"
End Select
End Function
Function Exists(userInput As String) As Boolean
Dim i As EList
For i = EList.[_Min] To EList.[_Max]
If userInput = ToString(i) Then
Exists = True
Exit Function
End If
Next
Exists = False
End Function
Sub Main()
Dim userInput As String
userInput = InputBox("type something:")
MsgBox Exists(userInput)
End Sub
First you declare your List as Enum. I have added only 3 items for the example to be as simple as possible. [_Min] and [_Max] indicate the minimum value and maximum value of enum (it's possible to tweak this but again, let's keep it simple for now). You declare them both to be able to iterate over your EList.
ToString() method returns a String representation of Enum. Any VBA developer realizes at some point that it's too bad VBA is missing this as a built in feature. Anyway, you've got your own implementation now.
Exists takes whatever userInput stores and while iterating over the Enum EList matches against a String representation of your Enum. It's an overkill because you need to call many methods and loop over the enum to be able to achieve what a simple Dictionary's Exists method does in one go. This is mainly why I wouldn't recommend using Enums for your specific problem.
Then in the end you have the Main sub which simply gathers the input from the user and calls the Exists method. It shows a Message Box with either true or false which indicates if the String exists as an Enum type.
Just use the Select Case with a list:
Select Case entry
Case item1,item2, ite3,item4 ' add up to limit for Case, add more Case if limit exceeded
do stuff for being in the list
Case Else
do stuff for not being in list
End Select

Public array to shuffle values between subroutines?

How do I get a public array whose values are set within a subroutine and do not get cleared at the end of the sub in which they were set?
I tried to get:
Public GlobalArray() as Variant
Sub One()
ReDim GlobalArray(0 to 2)
GlobalArray=Array("0","1","2")
End Sub
Sub Two()
Check = GlobalArray(2)
End Sub
such that Check = 2, but I get thrown an error in sub Two complaining about a lack of values in GlobalArray (in fact, even sub One complains that there is no GlobalArray to put things in).
Basically, I have a procedure (One) pulling data from disparate sources, doing some stuff with it, letting the user do some things in Excel, and then running a new subroutine (Two) that uses both the user's input and some of the arrays from sub One.
The Public GlobalArray() variable must be declared in a module. It will not work if it is declared at the top of either a Worksheet or the ThisWorkbook module. Try:
'// Must be declared in a module
Public GlobalArray() As Integer
'// These routines can be in worksheet/thisworkbook modules along side events etc Or in a module
Sub One()
ReDim GlobalArray(0 To 2)
GlobalArray(0) = 0
GlobalArray(1) = 1
GlobalArray(2) = 2
End Sub
Sub Two()
check = GlobalArray(2)
MsgBox (check)
End Sub
Instead of a public variable you could pass it to the second function:
Sub One()
Dim GlobalArray(0 To 2) As Integer
GlobalArray(0) = 0
GlobalArray(1) = 1
GlobalArray(2) = 2
Two GlobalArrayToMe:=GlobalArray
End Sub
Sub Two(ByRef GlobalArrayToMe() As Integer)
check = GlobalArrayToMe(2)
MsgBox (check)
End Sub
This is not VBA and won't compile: GlobalArray=("0","1","2")
You could instead use: GlobalArray = Array("0", "1", "2")
but that requires declaring Public GlobalArray() As Variant
Otherwise assign the array elements one by one as in #Readfidy's answer.