VB6 Tuple Equivalent? - vb6

I'm porting some C# code to VB6 because legacy applications. I need to store a list of pairs. I don't need to do associative lookups, I just need to be able to store pairs of items.
The snippet I'm porting from looks like this:
List<KeyValuePair<string, string>> listOfPairs;
If I were to port this to C++, I'd use something like this:
std::list<std::pair<string, string> > someList;
If this were python I'd just use a list of tuples.
someList.append( ("herp", "derp") )
I'm looking for a library type, but will settle for something else if necessary. I'm trying to be LAZY and not have to write cYetAnotherTinyUtilityClass.cls to get this functionality, or fall back on the so-often-abused string manipulation.
I've tried googling around, but VB6 is not really documented well online, and a lot of what's there is, well challenged. If you've ever seen BigResource, you'll know what I mean.

Collections of Variants can be quite flexible and unless you are really beating on them performance is not an issue:
Private Sub SomeCode()
Dim Pair As Variant
Dim ListOfPairs As Collection
Set ListOfPairs = New Collection
With ListOfPairs
Pair = Array("this", "that")
.Add Pair
.Add Array("herp", "derp")
.Add Array("weet", "tweet")
MsgBox .Item(1)(0) 'Item index is base-1, array index base-0.
Pair = .Item(2)
MsgBox Pair(1)
ReDim Pair(1)
Pair(0) = "another"
Pair(1) = "way"
.Add Pair
MsgBox .Item(4)(1)
End With
End Sub

If its literally just for storage you can use a Type:
Public Type Tuple
Item1 As String
Item2 As String
End Type
Its a bit more concise than needing a class to do the storage.
The problem with Types (known more widely as UDTs) is that there are restrictions on what you can do with them. You can make an array of a UDT. You cannot make a collection of a UDT.
In terms of .Net they're most similar to Struct.
There's a walkthrough of the basics here or here.

I had a similar scenario and used Dictionary by including a reference to Microsoft Scripting Runtime library in my VB6 project. This was suggested by a colleague of mine and worked really well.
Dim dictionary As New Dictionary
Dim index As Integer
dictionary.Add "Index1", "value for first index"
dictionary.Add "Index2", "value for second index"
'To get the value for a key
Debug.Print dictionary("Key1")
'To get the value for all keys
For index = 0 To UBound(dictionary.Keys)
Debug.Print dictionary.Keys(index) & "=" & dictionary(dictionary.Keys(index))
Next index

List Class? (see VB section):
http://msdn.microsoft.com/en-us/library/6sh2ey19#Y0
Dictionary Class?
http://msdn.microsoft.com/en-us/library/xfhwa508

You could use a Collection
dim c as new collection
c.add "a", "b"

Related

Using one variable for multiple items data in descriptive programming

I know that with Descriptive programming you can do something like this:
Browser("StackOverflow").Page("StackOverflow").Link("text:=Go To Next Page ", "html tag:=A").Click
But is it possible to create some kind of string so I can assign more than one data value and pass it as single variable? I've tried many combinations using escape characters and I always get error.
For example in the case above, let's say I have more properties in the Page object, so I'd normally have to do something like this:
Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")...etc...
But I'd like to pass "name:=StackOverflow", "html id:=PageID" as a single variable, so when writing many objects I'd only have to write:
Browser(BrowserString).Page(PageString).WebEdit("name:=asdfgh")
And the first part would remain static, so if the parents' data needs to be modified I'd only have to modify two variables and not all the objects created in all libraries.
Is it possible?
If I was not clear enough please let me know.
Thank you in advance!
I think what you're looking for is UFT's Description object
This allows you finer grained control on the description since in descriptive programming all values are regular expressions but with Description you can turn the regular expression functionality off for a specific property.
Set desc = Description.Create()
desc("html tag").Value = "A"
desc("innertext").Value = "More information..."
desc("innertext").RegularExpression = False
Browser("Example Domain").Navigate "www.example.com"
Browser("Example Domain").Page("Example Domain").WebElement(desc).Click
If you want to represent this with plain string then it's a bit more of a problem, you can write a helper function but I'm not sure I would recommend it.
Function Desc(descString)
Set ret = Description.Create()
values = Split(descString, "::")
For Each value In values
keyVal = Split(value, ":=")
ret(keyVal(0)).Value = keyVal(1)
Next
Set Desc = ret
End Function
' Usage
Browser("StackOverflow").Page("StackOverflow").WebElement(Desc("html tag:=H2::innertext:=some text")).Click
Further reading about descriptive programming.
As an alternative to Motti's excellent answer, you could also Set a variable to match your initial descriptive object and then extend it as required:
Set myPage = Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")
after which you can then use
myPage.WebEdit("name:=asdfgh")
throughout the rest of the code, so long as the myPage object stays in scope...

Is there a faster way to get file metadata than by using the shell COM component?

Reading various answers here and elsewhere, I pieced together this bit to get the file metadata that I need:
Public Class windows_metadata_helper
Public Shared shell As New Shell32.Shell
Public Shared indices_of_interest As New Dictionary(Of Integer, String)
Public Shared path_index As Integer
Shared Sub New()
'snipped long piece code for figuring out the indices of the attributes that I need, they are stored in indices_of_interest, for example 0:Name
End Sub
Public Shared Function get_interesting_data(path) As Dictionary(Of String, String)
Dim fi As New IO.FileInfo(path)
Dim f_dir = shell.NameSpace(fi.DirectoryName)
Dim data As New Dictionary(Of String, String)
For Each item In f_dir.Items()
If f_dir.GetDetailsOf(item, path_index) = fi.FullName Then
For Each kvp In indices_of_interest
Dim val = f_dir.GetDetailsOf(item, kvp.Key)
If Not String.IsNullOrEmpty(val) Then data.Add(kvp.Value, val)
Next
Exit For
End If
Next
Return data
End Function
End Class
Its not the most efficient code in the world, namely getting the path attribute of each file in the directory to identify the file I'm actually interested in. Optimizing this to only read the path attribute of each file once makes it around 50% faster (tested by letting it take the first file it finds whether its the right one or not) but regardless, its far slower than expected.
It needs to fetch 24 attributes from each file and it needs to find around 20k files from within ~100k, currently this takes an entire hour.
Profiling tells me that CPU is the bottleneck and whatever is taking up the cycles I can't see since its 99% inside the Shell32.Folder.GetDetailsOf method.
Is there a faster way to get the metadata? Answer doesn't have to be vb or .net specific.
Since you are seeking maximum speed, I suggest that you enable Option Strict for your code and make the necessary modifications that will be suggested by the IDE. This will eliminate unnecessary type conversions.
For instance,
Public Shared Function get_interesting_data(path) As Dictionary(Of String, String)
should be:
Public Shared Function get_interesting_data(path As String) As Dictionary(Of String, String)
Instead of enumerating the Shell32.Folder.Items collection, use the Shell32.Folder.ParseName Method to directly retrieve a FolderItem object. This object can be cast to a Shell32.ShellFolderItem that will allow using the ShellFolderItem.ExtendedProperty method.
There are two ways to specify a property. The first is to assign the
property's well-known name, such as "Author" or "Date", to sPropName.
However, each property is a member of a Component Object Model (COM)
property set and can also be identified by specifying its format ID
(FMTID) and property ID (PID). An FMTID is a GUID that identifies the
property set, and a PID is an integer that identifies a particular
property within the property set.
Specifying a property by its FMTID/PID values is usually more
efficient than using its name. To use a property's FMTID/PID values
with ExtendedProperty, they must be combined into an SCID. An SCID is
a string that contains the FMTID/PID values in the form "FMTID**PID",
where the FMTID is the string form of the property set's GUID. For
example, the SCID of the summary information property set's author
property is "{F29F85E0-4FF9-1068-AB91-08002B27B3D9} 4".
Many FMTID/PID values can be found under links presented at Windows Properties.
You can find the full property table here (scroll down).
Putting this together for some selected properties:
Public Shared Function get_interesting_data(path As String) As Dictionary(Of String, String)
Dim fi As New IO.FileInfo(path)
Dim f_dir As Shell32.Folder = shell.NameSpace(fi.DirectoryName)
' instead of enumerating f_dir.Items to find the file of interest
' directly retrieve the item reference
Dim item As Shell32.ShellFolderItem = DirectCast(f_dir.ParseName(fi.Name), Shell32.ShellFolderItem)
Dim scid_Bitrate As String = "{64440490-4C8B-11D1-8B70-080036B11A03} 4" ' Audio: System.Audio.EncodingBitrate
Dim scid_Title As String = "{F29F85E0 - 4.0FF9-1068-AB91-08002B27B3D9} 2" ' Core: System.Title
Dim scid_Created As String = "{B725F130-47EF-101A-A5F1-02608C9EEBAC} 15" ' Core: System.DateCreated
Dim scid_Copyright As String = "{64440492-4C8B-11D1-8B70-080036B11A03} 11" ' Core: System.Copyright
Dim scid_Publisher As String = "{64440492-4C8B-11D1-8B70-080036B11A03} 30" ' Media: System.Media.Publisher
Dim scid_FullDetails As String = "{C9944A21-A406-48FE-8225-AEC7E24C211B} 2" ' PropList: System.PropList.FullDetails
Dim bitrate As Object = item.ExtendedProperty(scid_Bitrate)
Dim title As Object = item.ExtendedProperty(scid_Title)
Dim created As Object = item.ExtendedProperty(scid_Created)
Dim copyright As Object = item.ExtendedProperty(scid_Copyright)
Dim publisher As Object = item.ExtendedProperty(scid_Publisher)
Dim fullDetails As Object = item.ExtendedProperty(scid_FullDetails)
Dim data As New Dictionary(Of String, String)
' save the retrieved properties
Return data
End Function
I do not know if this technique of retrieving the properties is faster than you have currently using GetDetailsOf, but the other changes should make some improvement.

VB6 - Items not adding to collection

I'm having problems building a collection of data. The problem code is as follows:
'Basic defitions are as follows:
Private mCol As Collection
Dim mcnn As ADODB.Connection
Dim mrs As New ADODB.Recordset
Dim uCustomClass As CustomClass
On Error GoTo 0
'set Find to false to catch any errors
Find = False
'checks for an active connection and then..
Set mCol = Nothing
Set mCol = New Collection
With mrs
.Open AN_SQL_SELECT_STATEMENT , mcnn, adOpenForwardOnly, adLockOptimistic
While Not .EOF
Set uCustomClass = New CustomClass
Set uCustomClass.Connection = mcnn
uCustomClass.CustomerName = NullToEquiv(.Fields("customer_name").Value,NULL_STRING)
uCustomClass.NumberOfOrders = NullToEquiv(.Fields("num_of_orders").Value, NULL_LONG)
uCustomClass.FavoriteColour = NullToEquiv(.Fields("favorite_colour").Value, NULL_STRING)
'Cache orginal values in case the keys change
uCustomClass.CacheOriginalValues
'add to collection
mCol.Add uCustomClass
.MoveNext
Wend
Now the result of this in run time is that the uCustomClass tree structure looks like:
-uCustomClass
+connection
count
+ mcnn
-mCol
+Item1
+Item2
+Item3
+mrs
mvarChangedCount
+NewEnum
It's all good bar I'm not getting Item1, Item2 and Item3 directly under the uCustomClass but only in mCol. I've what appears to be the exact same code running elsewhere for a different custom class and I'm getting what I want e.g.
-uCustomClassThatWorks
+connection
count
+ mcnn
-mCol
+Item1
+Item2
+Item3
+mrs
mvarChangedCount
+NewEnum
+Item1
+Item2
+Item3
Any ideas where the problem might be?
Not sure how uCustomClass would ever get those items added. Is there some missing code or something?
One point worth making is that collections can't have the same keys more than once, which would explain why they are able to be added in one area, but not able to be added again. There might be something that is even trimming strings or something that would aggravate the situation. So just make sure your keys are unique.

Is Nothing comparison gives type mismatch

I am trying to check if the 'Listivew.Tag property is nothing'.
I used to do the 'Is Nothing' check universally for all scenarios as first check to avoid errors
Can someone explain how to do it in VB 6?
If Not .lvwLocation.Tag Is Nothing Then
'COMPANY
str = str & IIf(Len(.lvwLocation.Tag) > 0, " and u.location_id in " & .lvwLocation.Tag, "")
End If
Gives error 'type-mismatch'
Nothing is a valid value for Object variables, and Is is the way to compare object pointers.
But a VB6 control's Tag property is a String, and VB6's String type is not an Object; it's a primitive type. That means a String variable can't be assigned Nothing -- its emptiest possible value is the empty string. (And an Object variable can't be assigned a String value.) For strings just use the same equality/inequality/comparision operators that you use for other primitive (numeric/boolean/date) types:
If .lvwLocation.Tag <> "" Then ...
In VB6 it appears that using Is Nothing to compare Objects works, Every other data type that I tried did not. In .Net Nothing represents the default value of any data type and will work like you expect.
Dim test as Object
If Not test Is Nothing Then
/////
End If
Since it appears the data type of th Tag property in VB6 is a string. I would use something like:
If .lvwLocation.Tag <> "" Then
/////
End If

Sorting a collection in classic ASP

It's quite a simple question - how do I sort a collection?
I've got a CSV file with rows in a random order. I'd like to sort the rows according to the date in one column. Do I add the rows to a recordset? Can I sort with a Scripting.Dictionary?
I've clearly been spoilt with .NET and Linq, and now I find myself back in the land of classic asp, realising I must have known this 7 years ago, and missing generics immensely. I feel like a complete n00b.
In this case I would get help from big brother .net. It's possible to use System.Collections.Sortedlist within your ASP app and get your key value pairs sorted.
set list = server.createObject("System.Collections.Sortedlist")
with list
.add "something", "YY"
.add "something else", "XX"
end with
for i = 0 to list.count - 1
response.write(list.getKey(i) & " = " & list.getByIndex(i))
next
Btw if the following .net classes are available too:
System.Collections.Queue
System.Collections.Stack
System.Collections.ArrayList
System.Collections.SortedList
System.Collections.Hashtable
System.IO.StringWriter
System.IO.MemoryStream;
Also see: Marvels of COM .NET interop
I'd go with the RecordSet approach. Use the Text Driver. You'll need to change the directory in the connection string and the filename in the select statement. the Extended Property "HDR=Yes" specifies that there's a header row in the CSV which I suggest as it will make writing the psuedo SQL easier.
<%
Dim strConnection, conn, rs, strSQL
strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\inetpub\wwwroot\;Extended Properties='text;HDR=Yes;FMT=Delimited';"
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open strConnection
Set rs = Server.CreateObject("ADODB.recordset")
strSQL = "SELECT * FROM test.csv order by date desc"
rs.open strSQL, conn, 3,3
WHILE NOT rs.EOF
Response.Write(rs("date") & "<br/>")
rs.MoveNext
WEND
rs.Close
Set rs = Nothing
conn.Close
Set conn = Nothing
%>
It's been a long time for me too. IIRC you don't have an option out of the box.
If I were you I'd put all the data in an array and then sort the array. I found a QuickSort implementation here: https://web.archive.org/web/20210125130007/http://www.4guysfromrolla.com/webtech/012799-3.shtml
Also look at the "Bubble Sort", works excellent with those classic asp tag cloud.
https://web.archive.org/web/20180927040044/http://www.4guysfromrolla.com:80/webtech/011001-1.shtml
A late late answer to this, but still of value.
I was working with small collections so could afford the approach where I inserted the item in the correct place on each occasion, effectively reconstructing the collection on each addition.
The VBScript class is as follows:
'Simple collection manager class.
'Performs the opration of adding/setting a collection item.
'Encapulated off here in order to delegate responsibility away from the collection class.
Class clsCollectionManager
Public Sub PopulateCollectionItem(collection, strKey, Value)
If collection.Exists(strKey) Then
If (VarType(Value) = vbObject) Then
Set collection.Item(strKey) = Value
Else
collection.Item(strKey) = Value
End If
Else
Call collection.Add(strKey, Value)
End If
End Sub
'take a collection and a new element as input parameters, an spit out a brand new collection
'with the new item iserted into the correct location by order
'This works on the assumption that the collection it is receiving is already ordered
'(which it should be if we always use this method to populate the item)
'This mutates the passed collection, so we highlight this by marking it as byref
'(this is not strictly necessary as objects are passed by reference anyway)
Public Sub AddCollectionItemInOrder(byref existingCollection, strNewKey, Value)
Dim orderedCollection: Set orderedCollection = Server.CreateObject("Scripting.Dictionary")
Dim strExistingKey
'If there is something already in our recordset then we need to add it in order.
'There is no sorting available for a collection (or an array) in VBScript. Therefore we have to do it ourself.
'First, iterate over eveything in our current collection. We have to assume that it is itself sorted.
For Each strExistingKey In existingCollection
'if the new item doesn't exist AND it occurs after the current item, then add the new item in now
'(before adding in the current item.)
If (Not orderedCollection.Exists(strNewKey)) And (strExistingKey > strNewKey) Then
Call PopulateCollectionItem(orderedCollection, strNewKey, Value)
End If
Call PopulateCollectionItem(orderedCollection, strExistingKey, existingCollection.item(strExistingKey))
Next
'Finally check to see if it still doesn't exist.
'It won't if the last place for it is at the very end, or the original collection was empty
If (Not orderedCollection.Exists(strNewKey)) Then
Call PopulateCollectionItem(orderedCollection, strNewKey, Value)
End If
Set existingCollection = orderedCollection
End Sub
End Class

Resources