I'm having some trouble compiling some VB code I wrote to split a string based on a set of predefined delimeters (comma, semicolon, colon, etc). I have successfully written some code that can be loaded inside a custom VB component (I place this code inside a VB.NET component in a plug-in called Grasshopper) and everything works fine. For instance, let's say my incoming string is "123,456". When I feed this string into the VB code I wrote, I get a new list where the first value is "123" and the second value is "456".
However, I have been trying to compile this code into it's own class so I can load it inside Grasshopper separately from the standard VB component. When I try to compile this code, it isn't separating the string into a new list with two values. Instead, I get a message that says "System.String []". Do you guys see anything wrong in my compile code? You can find an screenshot image of my problem at the following link: click to see image
This is the VB code for the compiled class:
Public Class SplitString
Inherits GH_Component
Public Sub New()
MyBase.New("Split String", "Split", "Splits a string based on delimeters", "FireFly", "Serial")
End Sub
Public Overrides ReadOnly Property ComponentGuid() As System.Guid
Get
Return New Guid("3205caae-03a8-409d-8778-6b0f8971df52")
End Get
End Property
Protected Overrides ReadOnly Property Internal_Icon_24x24() As System.Drawing.Bitmap
Get
Return My.Resources.icon_splitstring
End Get
End Property
Protected Overrides Sub RegisterInputParams(ByVal pManager As Grasshopper.Kernel.GH_Component.GH_InputParamManager)
pManager.Register_StringParam("String", "S", "Incoming string separated by a delimeter like a comma, semi-colon, colon, or forward slash", False)
End Sub
Protected Overrides Sub RegisterOutputParams(ByVal pManager As Grasshopper.Kernel.GH_Component.GH_OutputParamManager)
pManager.Register_StringParam("Tokenized Output", "O", "Tokenized Output")
End Sub
Protected Overrides Sub SolveInstance(ByVal DA As Grasshopper.Kernel.IGH_DataAccess)
Dim myString As String
DA.GetData(0, myString)
myString = myString.Replace(",", "|")
myString = myString.Replace(":", "|")
myString = myString.Replace(";", "|")
myString = myString.Replace("/", "|")
myString = myString.Replace(")(", "|")
myString = myString.Replace("(", String.Empty)
myString = myString.Replace(")", String.Empty)
Dim parts As String() = myString.Split("|"c)
DA.SetData(0, parts)
End Sub
End Class
This is the custom VB code I created inside Grasshopper:
Private Sub RunScript(ByVal myString As String, ByRef A As Object)
myString = myString.Replace(",", "|")
myString = myString.Replace(":", "|")
myString = myString.Replace(";", "|")
myString = myString.Replace("/", "|")
myString = myString.Replace(")(", "|")
myString = myString.Replace("(", String.Empty)
myString = myString.Replace(")", String.Empty)
Dim parts As String() = myString.Split("|"c)
A = parts
End Sub
'
'
End Class
Well, knowing nothing about Grasshopper, I'm just going to have to guess...
System.String [] is what .NET would print if you called ToString() on a string array. So, I'm gonna guess that you've given Grasshopper an array where it wants a single string.
So, with a little further guessing, how 'bout we try:
Dim parts As String() = myString.Split("|"c)
For I = 0 to parts.Length -1
DA.SetData(I, parts[I])
Well, I tried the code snippet you suggested... but it didn't quite work. I think the problem in the original code is that I'm trying to send a list of values (ie. parts) to a single output node. So, when I use DA.SetData(0,parts) I'm writing a list of values to the first output node of my compiled component. However, I think the problem is that the component doesn't know that parts is a list. In the example I gave before, if my incoming string is "123,456" then my result split list should have two values (123 and 456). I don't think I have declared parts to be a list. Do you have any ideas on how to do this? Again, if you click on the link in the original email (while using Internet Explorer... I'm not sure why Firefox isn't opening it) you should see a screenshot of the setup in the Grasshopper plugin which should help give you an idea of what's going on. Thanks again for your help.
Related
I'm writing an application in which I have to pass strings as parameters. Like these:
GetValue("InternetGatewayDevice.DeviceInfo.Description")
GetValue("InternetGatewayDevice.DeviceInfo.HardwareVersion")
CheckValue("InternetGatewayDevice.DeviceInfo.Manufacturer")
ScrambleValue("InternetGatewayDevice.DeviceInfo.ModelName")
DeleteValue("InternetGatewayDevice.DeviceInfo.ProcessStatus.Process.1")
The full list is about 10500 entries, and i tought that i'd be really lost in searching if i misspell something.
So I am trying to declare a namespace for every string segment (separated by ".") and declare the last as a simple class that widens to a String of its FullName (except the base app namespace):
Class xconv
Public Shared Widening Operator CType(ByVal d As xconv) As String
Dim a As String = d.GetType.FullName
Dim b As New List(Of String)(Strings.Split(a, "."))
Dim c As String = Strings.Join(b.Skip(1).ToArray, ".")
Return c
End Operator
End Class
So I'd have these declarations:
Namespace InternetGatewayDevice
Namespace DeviceInfo
Class Description
Inherits xconv
End Class
End Namespace
End Namespace
This way IntelliSense is more than happy to autocomplete that string for me.
Now I'd have to do this for every possible string, so I opted (in order to retain my sanity) to make a method that does that:
Sub Create_Autocomlete_List()
Dim pathlist As New List(Of String)(IO.File.ReadAllLines("D:\list.txt"))
Dim def_list As New List(Of String)
Dim thedoc As String = ""
For Each kl As String In pathlist
Dim locdoc As String = ""
Dim el() As String = Strings.Split(kl, ".")
Dim elc As Integer = el.Length - 1
Dim elz As Integer = -1
Dim cdoc As String
For Each ol As String In el
elz += 1
If elz = elc Then
locdoc += "Class " + ol + vbCrLf + _
"Inherits xconv" + vbCrLf + _
"End Class"
Else
locdoc += "Namespace " + ol + vbCrLf
cdoc += vbCrLf + "End Namespace"
End If
Next
locdoc += cdoc
thedoc += locdoc + vbCrLf + vbCrLf
Next
IO.File.WriteAllText("D:\start_list_dot_net.txt", thedoc)
End Sub
The real problem is that this is HORRIBLY SLOW and memory-intense (now i dot a OutOfMemory Exception), and I have no idea on how Intellisense would perform with the (not available in the near future) output of the Create_Autocomlete_List() sub.
I believe that it would be very slow.
So the real questions are: Am I doing this right? Is there any better way to map a list of strings to auto-completable strings? Is there any "standard" way to do this?
What would you do in this case?
I don't know how Visual Studio is going to perform with thousands of classes, but your Create_Autocomlete_List method can be optimized to minimize memory usage by not storing everything in memory as you build the source code. This should also speed things up considerably.
It can also be simplified, since nested namespaces can be declared on one line, e.g. Namespace First.Second.Third.
Sub Create_Autocomlete_List()
Using output As StreamWriter = IO.File.CreateText("D:\start_list_dot_net.txt")
For Each line As String In IO.File.ReadLines("D:\list.txt")
Dim lastDotPos As Integer = line.LastIndexOf("."c)
Dim nsName As String = line.Substring(0, lastDotPos)
Dim clsName As String = line.Substring(lastDotPos + 1)
output.Write("Namespace ")
output.WriteLine(nsName)
output.Write(" Class ")
output.WriteLine(clsName)
output.WriteLine(" Inherits xconv")
output.WriteLine(" End Class")
output.WriteLine("End Namespace")
output.WriteLine()
Next
End Using
End Sub
Note the use of File.ReadLines instead of File.ReadAllLines, which returns an IEnumerable instead of an array. Also note that the output is written directly to the file, instead of being built in memory.
Note Based on your sample data, you may run into issues where the last node is not a valid class name. e.g. InternetGatewayDevice.DeviceInfo.ProcessStatus.Process.1 - 1 is not a valid class name in VB.NET. You will need to devise some mechanism to deal with this - maybe some unique prefix that you could strip in your widening operator.
I'm also not sure how usable the resulting classes will be, since presumably you would need to pass an instance to the methods:
GetValue(New InternetGatewayDevice.DeviceInfo.Description())
It seems like it would be nicer to have Shared strings on a class:
Namespace InternetGatewayDevice
Class DeviceInfo
Public Shared Description As String = "Description"
Public Shared HardwareVersion As String = "HardwareVersion"
' etc.
End Class
End Namespace
So you could just reference those strings:
GetValue(InternetGatewayDevice.DeviceInfo.Description)
However, I think that would be a lot harder to generate without creating name clashes due to the various levels of nesting.
I want to conditionally append a string, derived from a function, to a string builder. The required condition is that the function is not returning a blank string ("").
The purpose of conditionally appending the string is to avoid AppendLine() appending a line when the string (returned from the function) being appended is empty.
My current code (wrapping the function in a condition calling the very same function):
Dim builder As New System.Text.StringBuilder()
builder.Append("Some text...").AppendLine()
If Not IsNothing(someFunction(someParameterAA, someParameterAB)) Then
builder.Append(someFunction(someParameterAA, someParameterAB)).AppendLine()
End If
If Not IsNothing(someFunction(someParameterBA, someParameterBB)) Then
builder.Append(someFunction(someParameterBA, someParameterBB)).AppendLine()
End If
builder.AppendLine().Append("...some text.")
Dim s As String = builder.ToString
MessageBox.Show(s)
It is my hope that a more efficient alternative exists (efficient in terms of the amount of code to be written). Ultimately, I'd like to avoid calling the same function twice however I cannot independently add the builder.Append line of code to my function. Is it instead possible to target builder.Append?
Example of the potential logic:
If `builder.Append()` inside the following brackets is not an empty string then:
(
builder.Append(someFunction(someParameterAA, someParameterAB)).AppendLine()
)
If anyone has a solution on the above, bear in mind the prequisite of concision (=< 2) lines of code additional to the builder.Append() line.
I'm open to any other suggestions.
Create another method to do the appending like so:
CheckBeforeAppend(someFunction(someParameterAA, someParameterAB), builder)
CheckBeforeAppend(someFunction(someParameterBA, someParameterBB), builder)
....
Public Sub CheckBeforeAppend(s As String, sb As StringBuilder)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
A simple refactor such as this shortens your original code and means you don't need to duplicate the null or empty check on the return value of your function.
And for the extension method (place this code in a Module):
<Extension()>
Public Sub CheckBeforeAppend(s As String, sb As StringBuilder)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
usage:
someFunction(someParameterAA, someParameterAB).CheckBeforeAppend(sb)
or for an extension on StringBuilder:
<Extension()>
Public Sub CheckBeforeAppend(sb As StringBuilder, s As String)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
usage:
builder.CheckBeforeAppend(someFunction(someParameterAA, someParameterAB))
You can avoid calling the function twice by storing the result of the function in a variable.
Dim myString As String = someFunction(someParameterAA, someParameterAB)
If myString <> "" Then
builder.Append(myString).AppendLine()
End If
myString = someFunction(someParameterBA, someParameterBB)
If myString <> "" Then
builder.Append(myString).AppendLine()
End If
This way you just call the function once and check your conditions with the variable. Also the code looks a lot smaller and more understandable.
We have a custom class library that has been built from the ground up that performs a variety of functions that are required for the business model in place. We also use VBA to automate some data insertion from standard Microsoft packages and from SolidWorks.
To date we have basically re-written the code in the VBA application macro's, but now are moving to include the class library into the VBA references. We've registered the class library for COM interop, and made sure that it is COM visible. The file is referencable, we have added the <ClassInterface(ClassInterfaceType.AutoDual)> _ tag above each of the Public Classes, so that intellisense 'works'.
With that said, the problem now arises - when we reference the class library, for this instance let's call it Test_Object, it is picked up and seems to work just fine. So we go ahead and try a small sample to make sure it's using the public functions and returning expected values:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim test As New Test_Object.Formatting
Dim t As String
t = test.extractNumber("abc12g3y45")
Target.Value = t
End Sub
This works as expected, returning 12345 in the selected cell/s.
However, when I try a different class, following the exact same procedure, I get an error (Object variable or With block variable not set). Code is as follows:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim test As New Test_Object.SQLCalls
Dim t As String
t = test.SQLNumber("SELECT TOP 1 ID from testdb.dbo.TESTTABLE") 'where the string literal in the parentheses is a parameter that is passed.
Target.Value = t
End Sub
This fails on the t = test.SQLNumber line. It also fails on another function within that SQLCalls class, a function that returns the date in SQL format (so it is not anything to do with the connection to the database).
Can anyone assist in what could be causing this error? I've googled for hours to no avail, and am willing to try whatever it takes to get this working.
Cheers.
EDIT: (added in the .SQLNumber() method)
Function SQLNumber(query As String) As Double
Dim tno As Double
Try
Using SQLConnection As SqlConnection = New SqlConnection(Connection_String_Current)
SQLConnection.Open()
SQLCommand = New SqlCommand(query, SQLConnection)
tno = SQLCommand.ExecuteScalar
End Using
Catch ex As System.Exception
MsgBox(ex.Message)
End Try
Return tno
End Function
For comparison, the extractNumber() method:
Function extractNumber(extstr As String) As Double
Dim i As Integer = 1
Dim tempstr As String
Dim extno As String = ""
Do Until i > Len(extstr)
tempstr = Mid(extstr, i, 1)
If tempstr = "0" Or tempstr = "1" Or tempstr = "2" Or tempstr = "3" Or tempstr = "4" Or tempstr = "5" Or tempstr = "6" Or tempstr = "7" Or tempstr = "8" Or tempstr = "9" Or tempstr = "." Then
extno = extno & tempstr
End If
i = i + 1
Loop
If IsNumeric(extno) Then
Return CDbl(extno)
Else
Return 0
End If
End Function
With the help of vba4all, we managed to delve down right to the issue.
When I tried to create a new instance of an object using Dim x as new Test_Object.SQLCalls, I was completely oblivious to the fact that I had not re-entered this crucial line:
<ClassInterface(ClassInterfaceType.None)> _.
Prior to doing this, I had this in my object explorer which has both the ISQLCalls and SQLCalls in the Classes section
But wait, ISQLCalls isn't a class, it's an interface!
By entering the <ClassInterface(ClassInterfaceType.None)> _ back in the SQLCalls class, the object explorer looked a bit better:
And low and behold, I could now create a new instance of the class, and the methods were exposed.
tldr:
I needed to explicitly declare the interface and use <InterfaceType(ComInterfaceType.InterfaceIsDual)> on the interface and <ClassInterface(ClassInterfaceType.None)> on the class.
Many thanks to vba4all, who selflessly devoted their time to assist in this issue.
I am trying to get a function for a class assignment to work however as the program hits the specific line in question it dies off and nothing after this line will execute. The program does not lock up, just the current execution path dies.
I have tried running debugging but much the same happens. Once I hit the link that should call a function from the object stored in the Arraylist element the break point at the actual function that should be called is not hit and nothing further happens.
Public Structure Appliances
' Create New Appliance object
Public Sub New(name As String, pusage As Double)
aName = name
aPUsage = pusage
End Sub
' Create New Washer Object
Public Sub New(name As String, pusage As Double, wusage As Double)
aName = name
aPUsage = pusage
aWUsage = wusage
End Sub
' Functions
Public Function getAName()
Return aName
End Function
Public Function getAPUsage()
Return aPUsage
End Function
Public Function getAWUsage()
Return aWUsage
End Function
Dim aName As String ' Appliance Name
Dim aPUsage As Double ' Appliane Power Usage
Dim aWUsage As Double ' Appliance Water Usage
End Structure
...
Public Class Form1
...
Dim appList As New ArrayList() ' Create an arraylist appliance objects
Public appTemp As Appliances ' To store appliance objects until they can be added to the arraylist
...
Private Function getAppInfo()
getAppInfo = Nothing
Do While fInStream.Peek() <> -1
s = fInStream.ReadLine() ' Get a line from the file and set s to it
Dim words As String() = s.Split(New Char() {","c}) ' Split the line contents along commas and set those parts into words
words(0) = words(0).Replace("_", " ") ' Reaplce underscores with spaces
If (words.Count = 3) Then ' If words contains the washer appliance
appTemp = New Appliances(words(0), Double.Parse(words(1)), Double.Parse(words(2)))
appList.Add(appTemp)
Else ' For all other appliances
appTemp = New Appliances(words(0), Double.Parse(words(1)))
appList.Add(appTemp)
End If
Loop
End Function
Private Function setUsage(name As String)
setUsage = Nothing
' Find appliance
For i = 0 To appList.Count
If (name = appList(i).getAName()) Then
If (name = "Washer") Then
s = appList(i).getWUsage() ' !!!This is the line where the execution dies at, nothing after this line is processed and the function call is not completed
txtbGPH.Text = s
End If
MsgBox("Test 1")
Exit For
ElseIf (i = appList.Count) Then
MsgBox("Appliance could not be found")
End If
Next
End Function
End Class
Use a List(Of X) instead of ArrayList if you are going to insert only one type:
Dim appList As New List(Of Appliances)
And I recommend you to declare your temp var inside the methods unless is necessary. Anyway, in this case you don't need it, you can add your var in this way:
appList.Add(New Appliances(words(0), Double.Parse(words(1))))
With this use (using lists) you won't need to use arraylistObj.Item(i).Method() and you can simply use the common way:
s = appList(i).getWUsage()
Nevermind, I figured it out just now. I did not know that arraylists are not "arraylists" but a collection. I thought maybe it would act like other collection oriented objects and that you have to use a .item(i) to access the elements, which turns out to be the case.
txtbGPH.text = appList.item(i).getAWusage()
produces the proper behavior and the rest of the code after the problematic line indicated in the OP executes as does the break point set at the called function.
i am trying to split a string up into separate lines with the following code, but for some reason it is also removing the spaces in the string.
Dim calculationText As String
calculationText = File.ReadAllText(fileName)
Dim fields() As String
fields = calculationText.Split(vbCrLf)
when i am in debugger mode, i look at fields, and every element has a line of the string but all the spaces and tabs are removed.
any reason for this?
If you are reading from a file, can you use:
Sub Main()
Dim fields As New List(Of String)
' read file into list
Using sr As System.IO.StreamReader = My.Computer.FileSystem.OpenTextFileReader(filename)
Try
Do While sr.Peek() >= 0
fields.Add(sr.ReadLine())
Loop
Finally
If sr IsNot Nothing Then sr.Close()
End Try
End Using
' check results
For Each line As String In fields
Console.WriteLine(line)
Next
End Sub
How 'bout:
Dim fields() As String = File.ReadAllLines(fileName)
As for why string.Split() is doing weird things...
vbCrLf is a string, and there's not an overload for string.split that accepts a single string parameter. If he were to turn on Option Explicit it wouldn't even compile, but since it's off, vbCrLf can be interpreted as an array of characters. And in this code, that's exactly what happens:
Sub Main()
Dim z As String = "The quick brown" & vbCrLf & " fox jumps over the lazy dogs."
Dim a() As String = z.Split(vbCrLf)
For Each c As String In a
Console.WriteLine(c)
Next
Console.ReadKey(True)
End Sub
You'll see two line breaks between the 1st and 2nd parts of that string. Something else is stripping out the spaces. Can you share the larger code block?
Gotta say I've never seen it do that, and I've used String.Split extensively. Are they really really gone, or is it a trick of the debugger?
There's not actually any .Split method that takes one string as the parameter, so the VB compiler would be doing "things" behind the scenes to pick a different overload. To try and force the correct overload, you could try calculationText.Split(vbCrLf.ToCharArray()). I doubt it will help, but you never know :-)