Looping Class Property with Do While Loops - vb.net

I have a code like below to find a category property with named "Model" and after that i will get the Model's name.
But as you can see "parent" property shows the upper level of parameter. My parameters in parameter groups and its like cascaded. And i don't know in which level they are currently i'm using below code but it's not sufficient because if i have a parameter very in lower levels i had to write this elseif conditions.
Is there any quick solution to make it easier and wise way?
Public Class ParameterInfoClass
Public Shared Sub GetSubvar(ByVal ParameterGroups As IScrNamedObjectList)
Dim ParameterGroup As IScrParameterGroup
Dim nParameterGroup As Integer
Dim ParameterClass As String
nParameterGroup = ParameterGroups.count
For i As Integer = 0 To nParameterGroup - 1
ParameterGroup = ParameterGroups.item(i)
If ParameterGroup.parent.category.name = "Model" Then
ParameterClass = ParameterGroup.parent.name
ElseIf ParameterGroup.parent.parent.category.name = "Model" Then
ParameterClass = ParameterGroup.parent.parent.name
ElseIf ParameterGroup.parent.parent.parent.category.name = "Model" Then
ParameterClass = ParameterGroup.parent.parent.parent.name
'...
'This should be continue like this because i don't know in which level i will find the category name as "Model"
'.
End If
DataGridView1.Rows.Add(ParameterClass, ParameterGroup.name)
Next
End Sub
End Class
It would be great to make this section with correct solution. I thought like do-while loops can be one option but i dont know how to apply because focus is in here to look upper levels of parameter to find "Model" category after that i'm writing that Model's name.
For i As Integer = 0 To nParameterGroup - 1
ParameterGroup = ParameterGroups.item(i)
If ParameterGroup.parent.category.name = "Model" Then
ParameterClass = ParameterGroup.parent.name
ElseIf ParameterGroup.parent.parent.category.name = "Model" Then
ParameterClass = ParameterGroup.parent.parent.name
ElseIf ParameterGroup.parent.parent.parent.category.name = "Model" Then
ParameterClass = ParameterGroup.parent.parent.parent.name
'...
'This should be continue like this because i don't know in which level i will find the category name as "Model"
'.
End If

I don't have a lot of time to dig into the details, but recursion is something I love so I'm giving you a hint (I would help more but that's the time I have right now).
Instead of iterating through every possible parent level, you should create a simple recursive function which will look if it finds the "Model", then either return it or else look for it's parent by calling itself to look for it.
You would have to use this new function instead of your If ElseIf ElseIf... potentially infinite function.
I drafted something which kinda looks like what I mean:
Private Function GetParameterClass(rootClassName As rootClass) As String
If ParameterGroup.category.name = "Model" Then
Return ParameterGroup.category.name
End If
If ParameterGroup.parent IsNot Nothing Then
Return GetParameterClass(ParameterGroup.parent)
End If
Return ""
End Function
By calling a similar function, you will iterate recursively through every parent until it finds none, and the first time it finds "Model", it'll stop the recursion and return it.
Sorry for not being more precise, as I have to get back to work myself! I'll look on your thread this evening when I can, just in case. Have fun!
EDIT:
I'm not familiar with the class you're working with, so there's a good amount of guesswork in this edit. Here's how I would try to solve your issue:
Public Class ParameterInfoClass
Public Shared Sub GetSubvar(ByVal ParameterGroups As IScrNamedObjectList)
For Each parameterGroup As IScrParameterGroup In ParameterGroups
Dim parameterClass As String = GetParameterClassName(parameterGroup)
If parameterName <> "" Then
DataGridView1.Rows.Add(parameterClass, parameterGroup.Name)
End If
Next
End Sub
Private Shared Function GetParameterClassName(parameterGroup As IScrParameterGroup) As String
If parameterGroup.category.name = "Model" Then
Return parameterGroup.name
End If
If parameterGroup.parent IsNot Nothing Then
Return GetParameterClass(parameterGroup.parent)
End If
Return ""
End Function
End Class
The main idea behind GetParameterClassName is that it'll either find the parameterGroup.category.name = "Model", or else it'll return an empty string. I replaced the way you were planning to iterate with a For Each loop, which should work well with most lists, but you might need to ajust that part is IScrNamedObjectList is not or doesn't contains a list or array or something.
Whenever a GetParameterClassName is found, then the parameterClass is not empty, so we can add these informations to the DataGridView1.
You can ask your questions in the comments if you have any, and I'll be happy to oblige. I love recursion!

Related

VBA 7.1 Setting multiple class properties by looping through a collection

self-taught VBA noob here. If I'm breaching ettiquette or asking something that everyone else knows already I'm sorry. Also, if I'm doing things that appear insane, it's because it is the only way I can either think of or actually make work. There's a department here at my work than can turn my makeshift code into something decent, but I have to give them a workable model first.
I have two programs with native VBA. One is a terminal emulator which I'm using to scrape mainframe data and to construct a custom class object, and then intend to pass it to MS Excel for number crunching. I am stuck with VBA until I can convince the IT folks that I am worthy of a Visual Studio license and scripting access. Also I have to pass the class in memory and not a spreadsheet in case of a program crash; no loose, easily recoverable data in lost files allowed.
The data is an invoice that has up to 99 lines, each line can bill an item or a service. The invoice is a custom invoice class, and each line is a custom line class contained in a collection of lines. I have everything built and working, but I'm stuck trying to set the line objects to their invoice line properties. Something with the effect of this:
For x = 1 To intLines
Invoice.Linex = cLines.Item(x)
Next x
hoping that in Excel I can use the invoice like this:
currTotalChrg = Invoice.Line01.Charge + Invoice.Line02.Charge
I've looked at the CallByName function but couldn't get it to work, and couldn't find an online example to show me how to set it up properly. Without that, I don't know how to make what I've seen others call a wrapper to construct and execute the lines. If I must, I can construct a SelectCasenstein to do the job, but there's got to be a better way. Since I can't post code (proprietary issues and government regulations), I am perfectly fine with vague answers; I can figure out the nuts and bolts if pointed in the right direction.
Thanx for the time and help!
Seems you want an Invoice collection class that holds InvoiceLineItem objects and exposes a TotalAmount property.
You can't edit module/member attributes directly in the VBE, but if you want to be able to iterate the line items of an invoice with a nice For Each loop, you'll have to find a way. One way is to export the class and edit it in your favorite text editor to add the attributes, save it, and then re-import it into your VBA project. Next release of Rubberduck will let you do that with "annotations" (magic comments), which I'm also including here:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Invoice"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Public Const MAX_LINE_ITEMS As Long = 99
Private Type TInvoice
InvoiceNumber As String
InvoiceDate As Date
'other members...
LineItems As Collection
End Type
Private this As TInvoice
Private Sub Class_Initialize()
this.LineItems = New Collection
End Sub
'#Description("Adds an InvoiceLineItem to this invoice. Raises an error if maximum capacity is reached.")
Public Sub AddLineItem(ByVal lineItem As InvoiceLineItem)
Attribute AddLineItem.VB_Description = "Adds an InvoiceLineItem to this invoice."
If this.LineItems.Count = MAX_LINE_ITEMS Then
Err.Raise 5, TypeName(Me), "This invoice already contains " & MAX_LINE_ITEMS & " items."
End If
this.LineItems.Add lineItem
End Sub
'#Description("Gets the line item at the specified index.")
'#DefaultMember
Public Property Get Item(ByVal index As Long) As InvoiceLineItem
Attribute Item.VB_Description = "Gets the line item at the specified index."
Attribute Item.VB_UserMemId = 0
Set Item = this.LineItems(index)
End Property
'#Description("Gets an enumerator that iterates through line items.")
'#Enumerator
Public Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_Description = "Gets an enumerator that iterates through line items."
Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = this.LineItems.[_NewEnum]
End Property
'...other members...
You could implement the sum outside the class, but IMO that would be feature envy; an invoice wants to be able to tell you its total amount & quantity.
So I would expose properties for that:
'#Description("Gets the total amount for all line items.")
Public Property Get TotalAmount() As Double
Dim result As Double
Dim lineItem As InvoiceLineItem
For Each lineItem In this.LineItems
result = result + lineItem.Amount
Next
TotalAmount = result
End Property
'#Description("Gets the total quantity for all line items.")
Public Property Get TotalQuantity() As Double
Dim result As Double
Dim lineItem As InvoiceLineItem
For Each lineItem In this.LineItems
result = result + lineItem.Quantity
Next
TotalQuantity = result
End Property
And then you might as well...
'#Description("Gets the total net amount for all line items (including taxes, discounts and surcharges).")
Public Property Get TotalNetAmount() As Double
TotalNetAmount = TotalAmount - TotalDiscounts + TotalSurcharges + TaxAmount
End Property
From your post and the nature of your question I suspect your class has.. what, 99 properties, one for each line on the invoice?
I am stuck with VBA until I can convince the IT folks that I am worthy of a Visual Studio license and scripting access.
VBA is just as object-oriented a language as any other "real" language you could use with Visual Studio. The above solution is fairly similar to how I would have implemented it in C#, or VB.NET. If your VBA class has a member for every single possible invoice line, your thinking is wrong - not the language you're using.
Stop hating VBA for the wrong reasons. The editor sucks, get over it.
I have a partial answer for you: not exactly what you asked for, but it shows you a syntax that can do it.
I have a 'totals' class - a simple wrapper for a dictionary - that allows you to specify named fields and start adding up values. It's trivial, and there isn't much to be gained by doing it... But bear with me:
Dim LoanTotals As clsTotals
Set LoanTotals = New clsTotals
For Each Field In LoanFileReader.Fields
LoanTotals.CreateField Field.Name
Next Field
For Each LineItem In LoanFileReader
LoanTotals.Add "LoanAmount", LineItem!LoanAmount
LoanTotals.Add "OutstandingBalance", LineItem!OutstandingBalance
LoanTotals.Add "Collateral", LineItem!Collateral
Next LineItem
The implementation details in the class aren't terribly interesting - you can work out that it all ends in Debug.Print LoanTotals.Total("LoanAmount")
...But what if I implemented this?
Dim LoanTotals As clsTotals
Set LoanTotals = New clsTotals
For Each Field In LoanFileReader.Fields
LoanTotals.CreateCommand Field.Name, Field.MainframeCommand
Next Field
...With an internal implementation like this:
Public Sub ExecuteCommand(CommandName, ParamArray() Args())
' Wrapper for objMainService, ends a command to the COM interface of the Mainframe client
CallByName objMainService, CommandName, vbMethod, Args
End Sub
Altenatively, you can concatenate Shell commands to execute those mainframe functions.
...And now you've populated a VB class that encapsulates a primitive API for a set of functions supplied at runtime.
As I say: it's not quite what you wanted, but it might get you somewhat closer to the solution you need.
For completeness, here's the code for the 'Totals' class:
A VBA Class for aggregating totals on named fields specified at runtime:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "clsTotals"
Attribute VB_Description = "Simple 'Totals' class based on a Scripting.Dictionary object"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
' Simple 'Totals' class based on a Scripting.Dictionary object
' Nigel Heffernan, Excellerando.Blogspot.com April 2009
' As it's based on a dictionary, the 'Add' and 'Reset' methods
' support implicit key creation: if you use a new name (or you
' mistype an existing name) a new Totals field will be created
' Coding Notes:
' This is a wrapper class: 'Implements' is not appropriate, as
' we are not reimplementing the class. Or not very much. Think
' of it as the 'syntactic sugar' alternative to prefixing all
' our method calls in the extended class with 'Dictionary_'.
Private m_dict As Scripting.Dictionary
Attribute m_dict.VB_MemberFlags = 40
Attribute m_dict.VB_VarDescription = "(Internal variable)"
Public Property Get Sum(FieldName As String) As Double
Attribute Sum.VB_Description = "Returns the current sum of the specified field."
Attribute Sum.VB_UserMemId = 0
' Returns the current sum of the specified field
Sum = m_dict(FieldName)
End Property
Public Sub CreateField(FieldName As String)
Attribute CreateField.VB_Description = "Explicitly create a new named field"
' Explicitly create a new named field
If m_dict.Exists(FieldName) Then
Err.Raise 1004, "Totals.CreateField", "There is already a field named '" & FieldName & "' in this 'Totals' object."
Else
m_dict.Add FieldName, 0#
End If
End Sub
Public Sub Add(FieldName As String, Value As Double)
Attribute Add.VB_Description = "Add a numeric amount to the field's running total \r\n Watch out for implicit field creation."
' Add a numeric amount to the field's running total
' Watch out for implicit field creation.
m_dict(FieldName) = m_dict(FieldName) + Value
End Sub
Public Sub Reset(FieldName As String)
Attribute FieldName.VB_Description = "Reset a named field's total to zero \r\n Watch out for implicit key creation"
' Reset a named field's total to zero
' Watch out for implicit key creation
m_dict(FieldName) = 0#
End Sub
Public Sub ResetAll()
Attribute ResetAll.VB_Description = "Clear all the totals"
' Clear all the totals
m_dict.RemoveAll
Set m_dict = Nothing
End Sub
Public Property Get Fields() As Variant
Attribute Fields.VB_Description = "Return a zero-based vector array of the field names"
'Return a zero-based vector array of the field names
Fields = m_dict.Keys
End Property
Public Property Get Values() As Variant
Attribute Values.VB_Description = "Return a zero-based vector array of the current totals"
'Return a zero-based vector array of the current totals
Fields = m_dict.Items
End Property
Public Property Get Count() As Long
Attribute Count.VB_Description = "Return the number of fields"
'Return the number of fields
Count= m_dict.Count
End Property
Public Property Get Exists(FieldName As String) As Boolean
Attribute Count.VB_Description = "Return a zero-based vector array of the field names"
'Return True if a named field exists in this instance of clsTotals
Exists = m_dict.Exists(FieldName)
End Property
Private Sub Class_Initialize()
Set m_dict = New Scripting.Dictionary
m_dict.CompareMode = TextCompare
End Sub
Private Sub Class_Terminate()
m_dict.RemoveAll
Set m_dict = Nothing
End Sub
Comment out the Attribute statements if you can't import them into your project.

Display String From Text File in a Label Box in Visual Basic

So I am trying to make a code where the user inputs an ID number and receives the line of text that the ID corresponds to. I am having trouble with the code as I am unable to display the result (or correct result) in a label box.
For example:
If I type in ID 1, it displays the data that corresponds to ID 2, ID 2 corresponds to ID 3 and ID 3 ends the loop (there are only 3 records in the data currently).
I have included my code below
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
Filename = "NamesAndAges.txt"
FileOpen(1, Filename, OpenMode.Input,,,)
Dim ID As Integer
ID = txtSearch.Text
Dim Found As Boolean
Found = False
Do While Not EOF(1) And Found = False
If LineInput(1).Contains(ID) Then
lblDisplaySearch.Text = LineInput(1)
Found = True
Else MsgBox("Not Found")
End If
Loop
FileClose(1)
End Sub
Thanks in advance, would also really appreciate if anyone could explain the code they use as I am still a visual basic beginner.
Every time you call LineInput(1), it reads a line, so you're reading a line and checking if it contains ID then reading another line and setting lblDisplaySearch.Text to that value.
Try something like this:
Dim line As String
Do While Not EOF(1) And Found = False
line = LineInput(1)
If line.Contains(ID) Then
lblDisplaySearch.Text = line
Found = True
Else MsgBox("Not Found")
End If
Loop
I would strongly suggest simplifying this code, by using File.ReadLines:
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
Filename = "NamesAndAges.txt"
Dim line = File.ReadLines(Filename).FirstOrDefault(Function(x) x.Contains(txtSearch.Text))
If line Is Nothing Then
MsgBox("Not Found")
Exit Sub
End If
lblDisplaySearch.Text = line
End Sub
The primary advantage is that you don't need to manage the Do While loop. Instead, you are passing the match condition (Function(x) x.Contains(txtSearch.Text)) to the FirstOrDefault method, which internally will find the first line matching the condition and return it, or return Nothing if it's not found.
For Each and IEnumerable
VB.NET allows you to loop over a set of items without knowing or caring about the index within the set. For example:
For Each x As String In File.ReadLines(Filename)
'do something with the line, which is now in x
Next
In order to use For Each with an object, that object has to implement a specific interface — the IEnumerable interface.
Interfaces
Interfaces guarantee that a given object has, or implements, specific members. In this case, if an object implements the IEnumerable interface, that means it has a GetEnumerator method, which the For Each uses under the hood.
IEnumerable(Of T)
The object returned from File.ReadLines implements another more advanced generic interface called IEnumerable(Of T). With this interface, the compiler can figure out automatically that each step of the For Each will be parsing a string, and we don't need to specify it as a string:
For Each x In File.ReadLines(Filename)
'x is known to be a String here
Next
Lambda expressions
The condition "the line which contains the search text" is written as a lambda expression, which (in this case) is made into a method without an explicit name or definition. This is convenient, because we don't have to write this:
Function ContainsCondition(line As String, toFind As String) As Boolean
Return line.Contains(toFind)
End Function`
every time we want to express a condition this way.
Type of x in the condition
Because File.ReadLines returns an IEnumerable(Of T), in this case an IEnumerable(Of String), the compiler can figure out that the condition is working on strings, so we don't have to specify that x is a string within the condition.

Array of variables in VBA

I have an code length issue I would like to hear your expertise about.
I have a class, called MyClass, with several Properties, P1, P2,...P16.
The values I want to put in the properties are in an array, called MyArray.
Right now, what I am doing, and it is working fine is:
MyClass.P1 = MyArray(0)
...
MyClass.P16 = MyArray(15)
It takes a lot of lines, and the code is not very readable.
I would like to be able to loop through the variables, like
For i = 0 to 15
array_of_variables(0) = MyArray(0)
Next
However, I have no idea on how to create this 'array_of_variables'.
I have tried creating a property of the class as an Array, but that is not correct VBA :(.
Do you have any thoughts on how to achieve this?
Thanks a lot,
Maxime
You could use a collection if you wanted a dirty way to do it, setup your class with a single collection of properties with predefined keys(names) and also use this same format with the collection your going to pass to the class and look through each name finding the match between the 2 collections and transferring the values. i guess you could do the same thing with an array but you would have to make sure the data in them is aligned, unlike collections that have names to identify them arrays only have indexes.
try this
place the following in your "MyClass" Class Module
Public Properties As Variant
and here follows a possible exploitation of that
Option Explicit
Sub main()
Dim MyClassInstance As MyClass
Dim i As Long
Set MyClassInstance = New MyClass
With MyClassInstance
.Properties = Array(1, 2, 3, 4, 5, 6)
For i = LBound(.Properties) To UBound(.Properties)
MsgBox .Properties(i)
Next i
End With
End Sub
Thanks to your suggestions, I came up with something mixing them all.
In the class I created the property like this:
Property Let Target(index, Value)
Select Case index
Case 0:
P1 = Value
...
Case 15
P16 = Value
End Select
End Property
This way, I can loop like this:
For i = 0 to 15
MyClass.Target(i) = MyArray(i)
Next

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

Set a type in VBA to nothing?

I have defined a variable with an own type, say
Dim point As DataPoint
Public Type DataPoint
list as Collection
name as String
number as Integer
End Type
and I want to delete all values of the variable point at once. If it was a class, I would just use Set point = New DataPoint, or set Set point = Nothing, but how can I proceed if it's a type?
You can benefit from the fact that functions in VB have an implicit variable that holds the result, and that contains the default type value by default.
public function GetBlankPoint() as DataPoint
end function
Usage:
point = GetBlankPoint()
The standard way is to reset each member to its default value individually. This is one limitation of user-defined types compared to objects.
At the risk of stating the obvious:
With point
Set .list = Nothing
.name = ""
.number = 0
End With
Alternatively, you can create a "blank" variable and assign it to your variable each time you want to "clear" it.
Dim point As DataPoint
Dim blank As DataPoint
With point
Set .list = New Collection
.list.Add "carrots"
.name = "joe"
.number = 12
End With
point = blank
' point members are now reset to default values
EDIT: Damn! Beaten by JFC :D
Here is an alternative to achieve that in 1 line ;)
Dim point As DataPoint
Dim emptyPoint As DataPoint
Public Type DataPoint
list As Collection
name As String
number As Integer
End Type
Sub Sample()
'~~> Fill the point
Debug.Print ">"; point.name
Debug.Print ">"; point.number
point.name = "a"
point.number = 25
Debug.Print ">>"; point.name
Debug.Print ">>"; point.number
'~~> Empty the point
point = emptyPoint
Debug.Print ">>>"; point.name
Debug.Print ">>>"; point.number
End Sub
SNAPSHOT
One-liner:
Function resetDataPoint() As DataPoint: End Function
Usage:
point = resetDataPoint()
Another option is to use the reserved word "Empty" such as:
.number= Empty
The only issue is that you will need to change the number from integer to variant.
Using classes in VBA is usually a good practice in case it is not a single purpose solution or the class do not contain too many private attributes because if you want to adhere on OOP rules and keep your class safe, you should declare all the Let and Get properties for all private attributes of class. This is too much coding in case you have more than 50 private attributes. Another negative side of using classes in excel is fact, that VBA do not fully support the OOP. There is no polymorfism, overloading, etc.) Even you want to use an inheritance, you have to declare all the attributes and methods from the original class in the inherited class.
So in this case I would prefer the solution suggested by Jean-François Corbett or GSeng, i.e. to assign an empty variable of the same UDT as the variable you want to clear or to use a function which to me seems little bit more elegant solution because it will not reserve permanent memory for the emtpy variable of your UDT type.
For that is better to use classes, you can declare a class module with the name of your type, then declare all of your members as public, then automatically you can set to nothing and new for create and delete instances.
syntax will be somthing like this after you create the class module and named like your type:
'
Public List as Collection
Public Name as String
Public Number as Long
Private Sub Class_Initialize()
'Here you can assign default values for the public members that you created if you want
End Sub