VBA Class properties returning empty strings - vba

I am trying to create a class in VBA for the first time. I have looked up some solutions and I don't see anything wrong with my class, but when I run the test code, the integer returns correctly but the strings return empty:
Class
Property Let Name(strName As String)
pName = strName
End Property
Property Get Name() As String
Name = pName
End Property
Property Let Class(strClass As String)
pClass = strClass
End Property
Property Get Class() As String
Class = pClass
End Property
Property Let Aggro(intAggro As Integer)
pAggro = intAggro
End Property
Property Get Aggro() As Integer
Aggro = pAggro
End Property
Test Procedure
Sub ClassTest()
Dim Dog1 As New Critter
Dog1.Name = "Labrador"
Dog1.Class = "Canine"
Dog1.Aggro = 0
Debug.Print Dog1.Name 'returns ""
Debug.Print Dog1.Class 'returns ""
Debug.Print Dog1.Aggro 'returns 0
End Sub

The only thing you have wrong is you haven't define private variables to hold your property values. It appears the integer is working because Integer initializes to 0, and you are 'setting' the value to 0. Just add this to the top of your class and try again:
Private pName as String
Private pClass as String
Private pAggro as Integer
:D

Related

How can I revert a property to its default value?

Public Property Name() As String = "default_name"
Sub InitializeFields()
Name = String.Empty
'
'
'
Name = Name.GetDefaultValue.ToString ' an example
End Sub
Programmatically, how can I revert the default value "default_name" of any property?
Here's an example of how you ought to do this with an actual default value:
Imports System.ComponentModel
Public Class Thing
<DefaultValue("Hello World")>
Public Property WithDefault As String
Public Property WithoutDefault As String
End Class
Imports System.ComponentModel
Imports System.Reflection
Module Module1
Sub Main()
Dim something As New Thing
something.WithDefault = "First"
something.WithoutDefault = "Second"
If TrySetDefaultValue(something, NameOf(something.WithDefault)) Then
Console.WriteLine($"{NameOf(something.WithDefault)} reset to ""{something.WithDefault}""")
Else
Console.WriteLine($"No default value for {NameOf(something.WithDefault)}")
End If
If TrySetDefaultValue(something, NameOf(something.WithoutDefault)) Then
Console.WriteLine($"{NameOf(something.WithoutDefault)} reset to ""{something.WithoutDefault}""")
Else
Console.WriteLine($"No default value for {NameOf(something.WithoutDefault)}")
End If
End Sub
Public Function TryGetDefaultValue([object] As Object, propertyName As String, ByRef value As Object) As Boolean
Dim attribute = [object].GetType().GetProperty(propertyName).GetCustomAttribute(Of DefaultValueAttribute)()
If attribute Is Nothing Then
Return False
End If
value = attribute.Value
Return True
End Function
Public Function TrySetDefaultValue([object] As Object, propertyName As String) As Boolean
Dim [property] = [object].GetType().GetProperty(propertyName)
Dim attribute = [property].GetCustomAttribute(Of DefaultValueAttribute)()
If attribute Is Nothing Then
Return False
End If
Dim value = attribute.Value
[property].SetValue([object], value)
Return True
End Function
End Module
There's no out-of-the-box way to somehow revert a property to its (custom) initial value. Once it's changed, it's over; the original value is lost.
Assuming the property belongs to a class (and that it doesn't get modified in the constructor), you could retrieve the value by creating a temporary instance of the class. For example:
Class SomeClass
Public Property Name As String = "default_name"
Sub InitializeFields()
Name = String.Empty
'
'
'
Name = New SomeClass().Name
End Sub
End Class
However, that's not very robust and could get ugly really fast. The way I would do this is by storing the default value in a constant:
Private Const DefaultName As String = "default_name"
Public Property Name As String = DefaultName
Sub InitializeFields()
Name = String.Empty
'
'
'
Name = DefaultName
End Sub
And then you can do that for each property that you need to later access its original value.
Answering myself:
Imports System.Reflection
Imports System.ComponentModel
Public Class clsThisClass
' each property have attribute of default value
<DefaultValue(0)>
Public Property RecordCount() As Long = 0
<DefaultValue("a")>
Public Property SQL() As String = String.Empty
<DefaultValue(0)>
Public Property IndexID() As Long = 0
<DefaultValue("b")>
Public Property Name() As String = String.Empty
<DefaultValue("c")>
Public Property Title() As String = String.Empty
<DefaultValue("d")>
Public Property Document_No() As String = String.Empty
<DefaultValue("abc")>
Public Property Company_Code() As String = String.Empty
Sub InitializeFields()
With Me
RecordCount = 28
IndexID = 10
Name = "name"
Title = "title"
Document_No = "doc_no"
Company_Code = "com_code"
Debug.WriteLine(RecordCount)
Debug.WriteLine(IndexID)
Debug.WriteLine(Name)
Debug.WriteLine(Document_No)
Debug.WriteLine(Company_Code)
' revert or reset all properties to default value upon initialization
Dim aType As Type = GetType(clsThisClass)
' each property of class
For Each pi As System.Reflection.PropertyInfo In aType.GetProperties()
' grab assigned default value of the property
Dim attribute = Me.GetType().GetProperty(pi.Name.ToString).GetCustomAttribute(Of DefaultValueAttribute)()
Try
Dim value As Object = Nothing
' convert value type according to the type of the property
value = CTypeDynamic(value, GetType(Attribute))
If Not attribute Is Nothing Then
value = attribute.Value
' set default value
pi.SetValue(Me, value)
End If
Catch ex As Exception
End Try
Next
Debug.WriteLine(RecordCount)
Debug.WriteLine(IndexID)
Debug.WriteLine(Name)
Debug.WriteLine(Document_No)
Debug.WriteLine(Company_Code)
End With
End Sub
End Class
Output:
28
10
name
doc_no
com_code
0
0
a
b
c
d
abc
initialized all properties with a default value at once.
in my scenario, I have numerous classes and have several properties.
by doing these few lines of code. I can copy-paste in each class at initializing method. it saved lots of time for writing code.

How to find blank value from all member of class in vb.net

I have third party object which contain so many member with integer, string and Boolean. I want to update that record whose value is not null or blank
You can use reflection to achieve what you want:
Sub Main()
Dim obj As Test = new Test()
Dim type As Type = GetType(Test)
Dim info As PropertyInfo() = type.GetProperties()
For Each propertyInfo As PropertyInfo In info
Dim value As String = propertyInfo.GetValue(obj)
If propertyInfo.PropertyType = GetType(String) And String.IsNullOrEmpty(value)
' empty value for this string property
End If
Next
End Sub
public Class Test
Public Property Test As String
End Class

How to create a compound object in VBA?

I cannot make my way through the Microsoft help, which is great provided you know what the answer is already, so I'm stuck.
Is it possible for me to create my own compound object (I assume that this is the term) such that, for example, the object could be a person and would have the following sub-classes:
Firstname - String
Surname - String
Date of birth - Datetime
Gender - String (M/F accepted)
Height - Real number
Sorry if it seems like a very basic question (no pun intended) but I haven't used Visual Basic for a long time, and Microsoft Visual Basic was never my forté.
You should consider using class modules instead of types. Types are fine, but they're limited in what they can do. I usually end up converting my types to classes as soon as I need some more function than a type can provide.
You could create a CPerson class with the properties you want. Now if you want to return a FullName property, you can write a Property Get to return it - something you can't do with a type.
Private mlPersonID As Long
Private msFirstName As String
Private msSurname As String
Private mdtDOB As Date
Private msGender As String
Private mdHeight As Double
Private mlParentPtr As Long
Public Property Let PersonID(ByVal lPersonID As Long): mlPersonID = lPersonID: End Property
Public Property Get PersonID() As Long: PersonID = mlPersonID: End Property
Public Property Let FirstName(ByVal sFirstName As String): msFirstName = sFirstName: End Property
Public Property Get FirstName() As String: FirstName = msFirstName: End Property
Public Property Let Surname(ByVal sSurname As String): msSurname = sSurname: End Property
Public Property Get Surname() As String: Surname = msSurname: End Property
Public Property Let DOB(ByVal dtDOB As Date): mdtDOB = dtDOB: End Property
Public Property Get DOB() As Date: DOB = mdtDOB: End Property
Public Property Let Gender(ByVal sGender As String): msGender = sGender: End Property
Public Property Get Gender() As String: Gender = msGender: End Property
Public Property Let Height(ByVal dHeight As Double): mdHeight = dHeight: End Property
Public Property Get Height() As Double: Height = mdHeight: End Property
Public Property Get FullName() As String
FullName = Me.FirstName & Space(1) & Me.Surname
End Property
Then you can create a CPeople class to hold all of your CPerson instances.
Private mcolPeople As Collection
Private Sub Class_Initialize()
Set mcolPeople = New Collection
End Sub
Private Sub Class_Terminate()
Set mcolPeople = Nothing
End Sub
Public Property Get NewEnum() As IUnknown
Set NewEnum = mcolPeople.[_NewEnum]
End Property
Public Sub Add(clsPerson As CPerson)
If clsPerson.PersonID = 0 Then
clsPerson.PersonID = Me.Count + 1
End If
mcolPeople.Add clsPerson, CStr(clsPerson.PersonID)
End Sub
Public Property Get Person(vItem As Variant) As CPerson
Set Person = mcolPeople.Item(vItem)
End Property
Public Property Get Count() As Long
Count = mcolPeople.Count
End Property
Public Property Get FilterByGender(ByVal sGender As String) As CPeople
Dim clsReturn As CPeople
Dim clsPerson As CPerson
Set clsReturn = New CPeople
For Each clsPerson In Me
If clsPerson.Gender = sGender Then
clsReturn.Add clsPerson
End If
Next clsPerson
Set FilterByGender = clsReturn
End Property
With this class, you can For Each through all the instances (google custom class and NewEnum to see how to do that). You can also use a Property Get to return a subset of the CPerson instances (females in this case).
Now in a standard module, you can create a couple of CPerson instances, add them to your CPeople instance, filter them, and loop through them.
Public Sub FillPeople()
Dim clsPerson As CPerson
Dim clsPeople As CPeople
Dim clsFemales As CPeople
Set clsPeople = New CPeople
Set clsPerson = New CPerson
With clsPerson
.FirstName = "Joe"
.Surname = "Blow"
.Gender = "M"
.Height = 72
.DOB = #1/1/1980#
End With
clsPeople.Add clsPerson
Set clsPerson = New CPerson
With clsPerson
.FirstName = "Jane"
.Surname = "Doe"
.Gender = "F"
.Height = 62
.DOB = #1/1/1979#
End With
clsPeople.Add clsPerson
Set clsFemales = clsPeople.FilterByGender("F")
For Each clsPerson In clsFemales
Debug.Print clsPerson.FullName
Next clsPerson
End Sub
There's defintely more learning curve to creating classes, but it's worth it in my opinion.
I think you need to use TYPE syntax, like this:
TYPE person
Firstname As String
Surname As String
Date_of_birth As Date ' instead of Datetime
Gender As String '(M/F accepted)
Height As Single 'instead of Real number
END TYPE
Sub Test()
Dim aTest As person
End Sub

Accessing custom property's value gives 'Out of Memory' error when value is null

I'm trying to create a custom property in an excel sheet, then retrieve its value. This is fine when I don't use an empty string, i.e. "". When I use the empty string, I get this error:
Run-time error '7':
Out of memory
Here's the code I'm using:
Sub proptest()
Dim cprop As CustomProperty
Dim sht As Worksheet
Set sht = ThisWorkbook.Sheets("control")
sht.CustomProperties.Add "path", ""
For Each cprop In ThisWorkbook.Sheets("control").CustomProperties
If cprop.Name = "path" Then
Debug.Print cprop.Value
End If
Next
End Sub
The code fails at Debug.Print cprop.value. Shouldn't I be able to set the property to "" initially?
With vbNullChar it works, sample:
Sub proptest()
Dim sht As Worksheet
Set sht = ThisWorkbook.Sheets("control")
' On Error Resume Next
sht.CustomProperties.Item(1).Delete
' On Error GoTo 0
Dim pathValue As Variant
pathValue = vbNullChar
Dim pathCustomProperty As CustomProperty
Set pathCustomProperty = sht.CustomProperties.Add("path", pathValue)
Dim cprop As CustomProperty
For Each cprop In ThisWorkbook.Sheets("control").CustomProperties
If cprop.Name = "path" Then
Debug.Print cprop.Value
End If
Next
End Sub
I think from the comments and the answer from Daniel Dusek it is clear that this cannot be done. The property should have at least 1 character to be valid, an empty string just isnt allowed and will give an error when the .Value is called.
So you Add this property with a length 1 or more string and you Delete the property again when no actual value is to be assigned to it.
As already mentioned it is not possible to set empty strings.
An easy workaround is to use a magic word or character, such as ~Empty (or whatever seems proof enough for you):
Dim MyProperty As Excel.CustomProperty = ...
Dim PropertyValue As String = If(MyProperty.Value = "~Empty", String.Empty, MyPropertyValue)
A slightly more expensive workaround but 100% safe is to start all the values of your custom properties with a character that you then always strip off. When accessing the value, systematically remove the first character:
Dim MyProperty As Excel.CustomProperty = ...
Dim PropertyValue As String = Strings.Mid(MyProperty.Value, 2)
You can write an extension to make your life easier:
<System.Runtime.CompilerServices.Extension>
Function ValueTrim(MyProperty as Excel.CustomProperty) As String
Return Strings.Mid(MyProperty.Value, 2)
End Function
Now you can use it like this: Dim MyValue As String = MyProperty.ValueTrim
Use a reversed principle when you add a custom property:
<System.Runtime.CompilerServices.Extension>
Function AddTrim(MyProperties As Excel.CustomProperties, Name As String, Value As String) as Excel.CustomProperty
Dim ModifiedValue As String = String.Concat("~", Value) 'Use ~ or whatever character you lie / Note Strig.Concat is the least expensive way to join two strings together.
Dim NewProperty As Excel.CustomProperty = MyProperties.Add(Name, ModifiedValue)
Return NewProperty
End Function
To use like this: MyProperties.AddTrim(Name, Value)
Hope this helps other people who come across the issue..
Based on the other answers and some trial and error, I wrote a class to wrap a Worksheet.CustomProperty.
WorksheetProperty:Class
Sets and Gets the value of a Worksheet.CustomProperty and tests if a Worksheet has the CustomProperty
VERSION 1.0 CLASS
Attribute VB_Name = "WorksheetProperty"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'#Folder("Classes")
'#PredeclaredId
Option Explicit
Private Type TMembers
Name As String
Worksheet As Worksheet
End Type
Private this As TMembers
Public Property Get Create(pWorksheet As Worksheet, pName As String) As WorksheetProperty
With New WorksheetProperty
Set .Worksheet = pWorksheet
.Name = pName
Set Create = .Self
End With
End Property
Public Property Get Self() As WorksheetProperty
Set Self = Me
End Property
Public Property Get Worksheet() As Worksheet
Set Worksheet = this.Worksheet
End Property
Public Property Set Worksheet(ByVal pValue As Worksheet)
Set this.Worksheet = pValue
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal pValue As String)
this.Name = pValue
End Property
Public Property Get Value() As String
Dim P As CustomProperty
For Each P In Worksheet.CustomProperties
If P.Name = Name Then
Value = P.Value
Exit Property
End If
Next
End Property
Public Property Let Value(ByVal pValue As String)
Delete
Worksheet.CustomProperties.Add Name:=Name, Value:=pValue
End Property
Public Property Get hasCustomProperty(pWorksheet As Worksheet, pName As String) As Boolean
Dim P As CustomProperty
For Each P In pWorksheet.CustomProperties
If P.Name = pName Then
hasCustomProperty = True
Exit Property
End If
Next
End Property
Public Sub Delete()
Dim P As CustomProperty
For Each P In Worksheet.CustomProperties
If P.Name = Name Then
P.Delete
Exit For
End If
Next
End Sub
Usage
I have several properties of my custom Unit class return a WorksheetProperty. It makes it really easy to sync my database with my worksheets.
Public Function hasMeta(Ws As Worksheet) As Boolean
hasMeta = WorksheetProperty.hasCustomProperty(Ws, MetaName)
End Function
Public Property Get Id() As WorksheetProperty
Set Id = WorksheetProperty.Create(this.Worksheet, "id")
End Property
Public Property Get CourseID() As WorksheetProperty
Set CourseID = WorksheetProperty.Create(this.Worksheet, "course_id")
End Property
Public Property Get Name() As WorksheetProperty
Set Name = WorksheetProperty.Create(this.Worksheet, "unit_name")
End Property
Simple Usage
'ActiveSheet has a CustomProperty
Debug.Print WorksheetProperty.hasCustomProperty(ActiveSheet, "LastDateSynced")
'Set a CustomProperty
WorksheetProperty.Create(ActiveSheet, "LastDateSynced").Value = Now
'Retrieve a CustomProperty
Debug.Print WorksheetProperty.Create(ActiveSheet, "LastDateSynced").Value

Error says x is not a member of y, but it is

I have a sp that I added to my linq designer, which generated the result class:
Partial Public Class web_GetTweetsByUserIDResult
Private _userid As Integer
Private _tweetid As Integer
Private _TweeterFeed As String
Public Sub New()
MyBase.New
End Sub
<Global.System.Data.Linq.Mapping.ColumnAttribute(Storage:="_userid", DbType:="Int NOT NULL")> _
Public Property userid() As Integer
Get
Return Me._userid
End Get
Set
If ((Me._userid = value) _
= false) Then
Me._userid = value
End If
End Set
End Property
<Global.System.Data.Linq.Mapping.ColumnAttribute(Storage:="_tweetid", DbType:="Int NOT NULL")> _
Public Property tweetid() As Integer
Get
Return Me._tweetid
End Get
Set
If ((Me._tweetid = value) _
= false) Then
Me._tweetid = value
End If
End Set
End Property
<Global.System.Data.Linq.Mapping.ColumnAttribute(Storage:="_TweeterFeed", DbType:="NVarChar(100)")> _
Public Property TweeterFeed() As String
Get
Return Me._TweeterFeed
End Get
Set
If (String.Equals(Me._TweeterFeed, value) = false) Then
Me._TweeterFeed = value
End If
End Set
End Property
End Class
However, in this one section of code where I am trying to use the "TweeterFeed" member of the result class I am getting the error, "Error 4 'TweeterFeed' is not a member of 'System.Data.Linq.ISingleResult(Of web_GetTweetsByUserIDResult)'."
My code in this section is, :
<WebMethod()> _
Public Function GetTweetsByUserID(ByVal userID As Integer) As List(Of SimpleTweet)
Dim result As New List(Of SimpleTweet)
Dim urlTwitter As String = "https://api.twitter.com/1/statuses/user_timeline.xml?include_entities=true&include_rts=true&screen_name={0}&count=20"
Dim lq As New lqDFDataContext
Dim var = lq.web_GetTweetsByUserID(userID).ToList()
If Not var Is Nothing Then
For Each twitterfeed In var
Dim listURL As String = String.Format(urlTwitter, var.TweeterFeed)
Dim tweetXML As XmlDocument = utils.GetXMLForURL(listURL)
Dim tweetnodelist As XmlNodeList = tweetXML.ChildNodes(1).ChildNodes
For Each node As XmlNode In tweetnodelist
Dim tweet As New SimpleTweet
tweet.CreatedAt = node.SelectSingleNode("created_at").InnerText
tweet.HTMLText = utils.ReturnTextWithHRefLink(node.SelectSingleNode("text").InnerText)
tweet.ID = node.SelectSingleNode("id").InnerText
tweet.Name = node.SelectSingleNode("user/name").InnerText
tweet.ScreenName = node.SelectSingleNode("user/screen_name").InnerText
tweet.Text = node.SelectSingleNode("text").InnerText
tweet.UserID = node.SelectSingleNode("user/id").InnerText
tweet.ProfileImageURL = node.SelectSingleNode("user/profile_image_url_https").InnerText
result.Add(tweet)
Next
Next
End If
Return result
End Function
Does anyone have any idea what is going on? As far as I see "TweeterFeed" is clearly a member of the class, I can't figure out why I would be getting this error.
You're using var.TweeterFeed when you should be using twitterFeed.TweeterFeed. twitterFeed is a result extracted from var which is a sequence of results.
Using a more descriptive variable name than var would probably have made this clearer to you :)
I have this class
Public Class Tamano
Private pWidth As Integer
Private pHeight As Integer
Public Property Width As Integer
Public Property Height As Integer
End Class
I got the compilation error message 'Height' is not a member of 'Tamaño' in IIS
To fix it, add Set and Get to the properties and it compiles.
Public Class Tamano
Private pWidth As Integer
Private pHeight As Integer
Public Property Width As Integer
Get
Return pWidth
End Get
Set(value As Integer)
pWidth = value
End Set
End Property
Public Property Height As Integer
Get
Return pHeight
End Get
Set(value As Integer)
pHeight = value
End Set
End Property
End Class
This might not be directly related to your question but It might help someone else.