VBA Complicated Getter, Setter syntax - vba

Hi I'm rather new to VBA I need to create an object with relativelly complicated Getter and Setter. To do this I am constantly checking with MSDN but clearly I am not understanding something because VBE keep highlighting lines starting and closing: Property (it appaently needs Get or Let??), Get(it apparently needs identifier), Let(it apparently needs identifier as well).
But I am trying to follow more concise notation where Get and Let methods are within a Property Statement which is used by Microsoft in its examples(see link above).
Can someone tell me where is my syntax wrong(or Microsoft's Documentation for that matter)???
Thank you
Private Matrix() As Vector
Property Transition()
Public Get(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
Return Matrix(Column, Row).Value(Period)
End Get
Public Let(Old_S As String, New_S As String, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Let
End Property

You are reading the documentation for VB.NET. That's why you are confused. The syntax for properties in VBA is different. In VBA, the Get and Let for a property are not grouped together. They need to be listed separately, essentially like two separate methods:
Private mMyProperty As String
Public Property Get MyProperty() As String
MyProperty = mMyProperty
End Property
Public Property Let Transition(Value As String)
mMyProperty = Value
End Property
For VBA reference material, try starting here.

Your issue is that you are reading .Net help files! :)
Assuming you have a valid Vector class, your properties need to bde defined like this:
Private Matrix() As Vector
Public Property Get Transition(Old_S As String, New_S As String, Period As Integer) As String
' Some Code
Transition = Matrix(Column, Row).Value(Period)
End Property
Public Property Let Transition(Old_S As String, New_S As String, Period As Integer, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Property
Note that the argument list for both procedures must match except that the Letter has an additional argument of the same type that the Getter returns.
It is also slightly unusual to have a Getter property that accepts arguments in VBA - that would more usually be implemented as a method.

Looks like you're using VB.Net syntax in VBA. That's not going to work, as they're entirely different languages. Here is a link to the proper documentation for the VBA Property Keyword.
Here is how you would write it in VBA.
Private Matrix() As Vector
Public Property Get MatrixValue(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
MatrixValue = Matrix(Column, Row).Value(Period)
End Property
Public Property Let MatrixValue(Old_S As String, New_S As String, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Property
But you're going to have trouble with the Get property. You can pass parameters into Get, but it's not exactly intuitive. I think what you're really looking for is a function.
Public Function GetMatrixValue(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
MatrixValue = Matrix(Column, Row).Value(Period)
End Function

Related

What's the elegant way to make sure that values of dictionary are called or set in a specific location?

Private Shared Property _dictOfCoinNameNameBasedOnMarketandSymbol As Dictionary(Of String, String)
Get
End Get
Set(value As Dictionary(Of String, String))
End Set
End Property
The usual way, if it's not a dictionary, is to turn the variable into property. Then we can put breakpoint in set and get.
The thing is setting a dictionary as property will make the compiler think I need a set method to set the dictionary. I want a set method to set an item in the property.
I want to be able to replace spread occurrences of
_dictOfCoinNameNameBasedOnMarketandSymbol("key") = "value"
into a code I can set breakpoint too. How should I?
I suppose I can just create a normal sub and replace all occurrences of _dictOfCoinNameNameBasedOnMarketandSymbol("key") = "value" into AssignCoinNameNameBasedOnMarketandSymbolWithKeyValue("key","value")
I wrote
Function GetCoinNameNameBasedOnMarketandSymbol(key As String) As String
Return _dictOfCoinNameNameBasedOnMarketandSymbol(key)
End Function
Sub AssignCoinNameNameBasedOnMarketandSymbol(key As String, coinname As String)
_dictOfCoinNameNameBasedOnMarketandSymbol(key) = coinname
End Sub
And then I will just manually all other occurrences of _dictOfCoinNameNameBasedOnMarketandSymbol. Then I can put breakpoints in AssignCoinNameNameBasedOnMarketandSymbol if I see _dictOfCoinNameNameBasedOnMarketandSymbol get assigned wrong value.
The problem with this technique is there isn't really anyway to enforce it automatically. I just have to go through the code and replace that one by one.
Are there more elegant ways?
If _dictOfCoinNameNameBasedOnMarketandSymbol is not a dictionary then changing it to property with proper get and set value would do just fine. I don't even have to change anything.
I think that I may have initially misinterpreted the question. I will leave my original answer below for completeness but I will answer here based on my new understanding.
It seems to me that what you actually need is an indexed property that will wrap the Dictionary, e.g.
Public Class SomeClass
Private Shared ReadOnly valuesByKey As New Dictionary(Of String, String)
Public Shared Property ValueByKey(key As String) As String
Get
Return valuesByKey(key)
End Get
Set
valuesByKey(key) = Value
End Set
End Property
End Class
You can now just get and set the ValueByKey property and never have direct access - read-only or otherwise - to the Dictionary object inside the class. You can also add whatever code you like before the Return statement in the getter and before or after the existing line of code in the setter.
ORIGINAL ANSWER:
What you want is a ReadOnly property. That's exactly how collections are exposed throughout the .NET Framework, e.g. Control.Controls, ComboBox.Items, DataSet.Tables, DataTable.Columns, DataTable.Rows, etc, etc. In your case, that would look like this:
Public Shared ReadOnly Property CoinNamesByMarketAndSymbol As New Dictionary(Of String, String)
I've taken the liberty of giving that property a sensible name. Note that this is an auto-property, i.e. one where you don't explicitly specify a getter or setter. Without using an auto-property, the equivalent code would this:
Private Shared _coinNamesByMarketAndSymbol As New Dictionary(Of String, String)
Public Shared ReadOnly Property CoinNamesByMarketAndSymbol As Dictionary(Of String, String)
Get
Return _coinNamesByMarketAndSymbol
End Get
End Property
Note that I have declared this property Public. I note that you have declared it Private but that seems to make little sense. What use could such a property be? If it should only be accessed internally then why would you not just use a field?
With the code above, you can now get the Dictionary from the property to get, add or remove items but you cannot replace the entire Dictionary, i.e. you can do this:
Dim coinName = SomeClass.CoinNamesByMarketAndSymbol(marketAndSymbol)
SomeClass.CoinNamesByMarketAndSymbol.Add(marketAndSymbol, coinName)
SomeClass.CoinNamesByMarketAndSymbol.Remove(marketAndSymbol)
But you cannot do this:
SomeClass.CoinNamesByMarketAndSymbol = New Dictionary(Of String, String)

Changing a Type to a Class causes ByRef parameters to act ByVal

I've heard advice to change from User Defined Type (UDT) to a regular Class in order to overcome the limitations of UDT, such as not being able to use For Each with a UDT.
I've also heard advice to change from a regular Class to UDT to overcome the Class limitation where you can't pass things BYREF, like...
'Function:
Public Function RemoveArticle (ByRef strMovieTitle As String)
'Expected input is like "Terminator, The"
strMovieTitle = Left(... 'removes the article.
End Function
That works fine for this call:
Dim strMovieTitle As String
strMovieTitle = "Terminator, The"
RemoveArticle strMovieTitle
But not this call:
Dim objMovie As MovieClass
objMovie.strMovieTitle = "Terminator, The"
objMovie.strMovieGenre = "Sci-Fi"
InvertArticle objMovie.strMovieTitle
Even though MovieClass defines
strMovieTitle As String
I can't go changing RemoveArticle (and every simple little function like it) to take a MovieClass parameter instead of a String parameter because there are other UDTs or Classes and String Variables that also need to use RemoveArticle.
What do I do if I need to use For Each and I also need to pass ByRef?
Is there a way a Class can work around the parameter problem?
(Using Excel 2010.)
Now I have understood your concern.
You simply can't take that approach to meet your goal. As Tim Williams has commented in your question, your best bet would be something like this:
Dim objMovie As MovieClass
Dim strMovieTitle As String
strMovieTitle = "Terminator, The"
objMovie.strMovieTitle = InvertArticle(strMovieTitle)
However, I see that this still does not satisfy your need.
My suggestion is as follows:
make your object internal, target properties Private and expose them with Property Let and Property Get. This way you can do the modifications you want to the properties either on set or on get (from within the class... rather than fixing things from outside the class).
Aside note, in regards to create a helper class (as someone has recommended to you): you could join into one class all those functions you use widely, such as RemoveArticle or InvertArticle. However, it requires to create an instance object every time you want to use them and, therefore, does not combine well with the recommendation I am giving to you (if you want just to simplify code). So having them in a Module as you do now is fine. Just to clarify: those recommendations they gave to you are unrelated to your question here.
Example A: on set
In you class MovieClass, rename first all the instances of strMovieTitle to pStrMovieTitle and add this to your code:
Private pStrMovieTitle As String
Public Property Let strMovieTitle (strIn As String)
pStrMovieTitle = InvertArticle(strIn)
End Property
Public Property Get strMovieTitle As String
strMovieTitle = pStrMovieTitle
End Property
The usage would be something like this:
Dim objMovie As MovieClass
objMovie.strMovieTitle = "Terminator, The" ' the article position gets rectified on assignation
objMovie.strMovieGenre = "Sci-Fi"
'InvertArticle objMovie.strMovieTitle ' => you don't need to do this call
Example B: on get
To keep your original string as it comes, and do apply your helpers when you get the property value. That way you always preserve the original string. However, this approach will need more rework and it's only worthy in cases where you have lots of ways to use that String in different parts of your code.
Private pStrMovieTitleSource As String
Public Property Let strMovieTitle (strIn As String)
pStrMovieTitleSource = Trim(strIn)
End Property
Public Property Get strMovieTitleSource () As String
strMovieTitleSource = pStrMovieTitleSource
End Property
Public Property Get strMovieTitleRoot () As String
strMovieTitleRoot = RemoveArticle(pStrMovieTitleSource)
End Property
Public Property Get strMovieTitle () As String
strMovieTitle = InvertArticle(pStrMovieTitleSource)
End Property
Hope it helps

How To Iterate Over Members of a Structure Array in VB.net Using Member Function

I have a vb.net enumeration that looks like this:
' define enumeration for keypad states
Enum KeyPadState
KEYPAD_NO ' no keypad
KEYPAD_UC ' upper case keypad
KEYPAD_LC ' lower case keypad
KEYPAD_NU ' numeric keypad
KEYPAD_SY ' symbol keypad
End Enum
I then defined a structure element to be used to translate the members of the above enumeration from enumeration values to string values and back again. The declared structure looks like below. Note the member functions that I have tried to insert. The "New" one is working.
' define keypad type look up structure
Private Structure KeyPadXlat
Dim KeyPadEnum As KeyPadState
Dim KeyPadStr As String
' initializer subroutine
Public Sub New(nKeyPadEnum As KeyPadState, nKeyPadStr As String)
KeyPadEnum = nKeyPadEnum
KeyPadStr = nKeyPadStr
End Sub
' translate string to enum
Public Function ToEnum(xKeyPadStr As String) As KeyPadState
For Each item As KeyPadXlat In ????
Next
End Function
' translate enum to string
Public Function ToStr(xKeyPadEnum As KeyPadState) As String
End Function
End Structure
The actual instance of the structure array is shown below with its initializer code.
Dim KeyPadLookUp() As KeyPadXlat = { _
New KeyPadXlat(KeyPadState.KEYPAD_NO, "KEYPAD_NO"), _
New KeyPadXlat(KeyPadState.KEYPAD_UC, "KEYPAD_UC"), _
New KeyPadXlat(KeyPadState.KEYPAD_LC, "KEYPAD_LC"), _
New KeyPadXlat(KeyPadState.KEYPAD_NU, "KEYPAD_NU"), _
New KeyPadXlat(KeyPadState.KEYPAD_SY, "KEYPAD_SY") _
}
So my question is with regard to the member functions I am trying to create to translate back and forth between the enumeration value and the string value. I have copied one of them here again for reference:
' translate string to enum
Public Function ToEnum(xKeyPadStr As String) As KeyPadState
For Each item As KeyPadXlat In ????
Next
End Function
What I need help with is how to write the code for the For Each loop so that it iterates across all of the elements of the structure array when being in a member function.
To be honest, you really don't need all that code. This should do it nicely.
Enum KeyPadState
KEYPAD_NO ' no keypad
KEYPAD_UC ' upper case keypad
KEYPAD_LC ' lower case keypad
KEYPAD_NU ' numeric keypad
KEYPAD_SY ' symbol keypad
End Enum
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim state As KeyPadState
state = KeyPadState.KEYPAD_LC
'this line will assign the name of the enum `state` to a string called `tempstring`
'It's hardly worth encapsulating it into a function so I've left it as is
'But if you want to provide consistent code, it would be better to.
Dim tempstring As String
tempstring = [Enum].GetName(GetType(KeyPadState), state)
Dim anyString As String = "KEYPAD_UC"
Dim tempState As KeyPadState
'the following line will try to parse `anyString` to an enum value of the same type as the variable to be assigned.
'In this case `state`
tempState = ParseToKeypadState(anyString)
End Sub
Private Function ParseToKeypadState(tempString As String) As KeyPadState
Dim returnValue As KeyPadState
If Not [Enum].TryParse(tempString, returnValue) Then
'handle parsing error here
End If
Return returnValue
End Function
There's all sorts wrong with your code there.
Firstly, I'd suggest that your naming conventions are poor. If you do as is done throughout the .NET Framework then you enumeration would look like this:
Enum KeyPadState
None
UpperCase
LowerCase
Numeric
Symbol
End Enum
That's clear and self-documenting.
Secondly, it is not recommended to use abbreviations like "Xlat". That's meaningless to anyone without prior knowledge. Is it so onerous to write "Translate" and then let Intellisense find it whenever you need to use it in code?
As for the implementation of your class, why does it need any methods at all? You are passing in the KeyPadState value and the text representation when you create an instance so what is there for those methods to do? Your structure should simply be a constructor and two properties:
Private Structure KeyPadStateTranslation
Public ReadOnly Property Value As KeyPadState
Public ReadOnly Property Text As String
Public Sub New(value As KeyPadState, text As String)
Me.Value = value
Me.Text = text
End Sub
End Structure
The property values are set when the instance is created and they are retrieved via the properties. Everything also has an appropriate name.
That said, you don't even need to provide the text because you can simply call ToString on the value to get it:
Private Structure KeyPadStateTranslation
Public ReadOnly Property Value As KeyPadState
Public ReadOnly Property Text As String
Public Sub New(value As KeyPadState)
Me.Value = value
Me.Text = value.ToString()
End Sub
End Structure
Also, the fact that that structure is declared Private is an issue. That indicates that it is declared inside another type. That's not right. Structures are first-class types, just like classes, so they belong in their own dedicated code file, just like classes.
What's the point of that structure at all though? You'd still have to loop through your array to find an instance that matches either a value or some text so it doesn't really help. A Dictionary might be better but you may as well just call ToString on a value if you need to convert that way and use Enum.Parse or .TryParse when you need to go the other way.

How to instantiate Class object with varying number of property values

Been working a lot with custom classes lately and I love the power you can have with them but I have come across something that I'm not able to solve and/or find anything helpful online.
I have a list of a class with properties I'm looking to only store information pulled from a database into.
Public Class CustomClass
Public _Values As String
Public _Variables As String
Public ReadOnly Property Values() As String
Get
Return _Values
End Get
End Property
Public ReadOnly Property Variables() As String
Get
Return _Variables
End Get
End Property
Sub New(ByVal values As String, ByVal variables As String)
_Values = values
_Variables = variables
End Sub
End Class
I will be iterating through some database entries, and I'm looking to store them into the appropriate property when I hit them (since I won't have them all available immediately, which is part of my problem). I want to just be able to add either the value or the variable at a time and not both of them, but since I have the sub procedure 'New' passing two arguments, it will always require passing them both. I've found the only way around this is by making them optional fields which I don't feel is the right way to solve this. Is what I'm looking to do possible with a class or would it be simpler by using a structure?
You can overload the constructor:
Friend Class Foo
' using auto-implement props:
Public Property Name As String ' creates a _Name backing field
Public Property Value as Integer
Public Sub New(newN as String, newV as Integer)
' access "hidden" backing fields if you want:
_Name = newN
_Value = newV
End Sub
Public Sub New() ' simple ctor
End Sub
Public Sub New(justName As String)
' via the prop
Name = justName
End Sub
End Class
You now have 3 ways to create the object: with full initialization, partial (name only) or as a blank object. You will often need a "simple constructor" - one with no params - for other purposes: serializers, Collection editors and the like will have no idea how to use the parameterized constructors and will require a simple one.
If rules in the App were that there was no reason for a MyFoo to ever exist unless both Name and Value being defined, implementing only the New(String, Integer) ctor enforces that rule. That is, it is first about the app rules, then about coding convenience.
Dim myFoo As New Foo ' empty one
myFoo.Name = "ziggy" ' we only know part of it
Since the default of string is nothing, you could pass nothing for the value you don't have. IE
Collection.Add(New CustomClass("My Value",Nothing))
Every type has a default, so this works with more than just strings.

How can I get a property name for a type without the need to instantiate an object of that type?

I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)