How do I prompt the user to input elements in a two dimensional array? And how do I then save the output?
I've figured out how write and print a program where the program provides the elements (see short version below), but I can't work out how to get the user to input the elements instead.
Many thanks.
Solfa(0, 0) = 11
Solfa(0, 1) = 12
Solfa(1, 0) = 21
Solfa(1, 1) = 22
TextBox1.Text = Solfa(0, 0) & " " & Solfa(0, 1) & vbCrLf & Solfa(1, 0) & " " & Solfa(1, 1)
Consider a grid (if your presentation framework has one).
Otherwise, you can use an multi-line textbox that is pre-populated with a comma-separated list of values, like this:
11, 12
21, 22
Then, you can parse the user-edited values again (the easiest way being)
For Each line in input.Split (vbCrLf)
For Each field in line.Split (", ".ToCharArray())
// Plug back into array.
// Don't forget bounds-checking.
Next
// Don't forget bounds-checking.
Next
Related
I am attempting to make a project on visual studio.
I have the following data in a combo box and I was wondering how I would be able to multiply the price (the CStr value) by the number of days the user selects, showing the total in another text box using a calculate button
{cmbPedigreeDog.Items.Add("African Hairless" & CStr(1.14))
cmbPedigreeDog.Items.Add("Boxer" & CStr(0.86))
cmbPedigreeDog.Items.Add("Chihuahua" & CStr(1.83))
cmbPedigreeDog.Items.Add("Dalmation" & CStr(0.65))
cmbPedigreeDog.Items.Add("Eskimo Dog" & CStr(1.14))
cmbPedigreeDog.Items.Add("Farm Collie" & CStr(0.95))
cmbPedigreeDog.Items.Add("GreyHound" & CStr(1.99))
cmbPedigreeDog.Items.Add("Husky" & CStr(1.85))
cmbPedigreeDog.Items.Add("Irish Setter" & CStr(0.65))
cmbPedigreeDog.Items.Add("Jack Russell Terrier" & CStr(1.77))
cmbPedigreeDog.Items.Add("King Charles Spaniel" & CStr(1.02))
cmbPedigreeDog.Items.Add("Labrador Retreiver" & CStr(1.74))
cmbPedigreeDog.Items.Add("Maltese" & CStr(1.47))
cmbPedigreeDog.Items.Add("Pug" & CStr(1.31))
cmbPedigreeDog.Items.Add("Rottweiler" & CStr(2.17))
cmbPedigreeDog.Items.Add("St Bernard" & CStr(1.63))
cmbPedigreeDog.Items.Add("Tibetan Mastiff" & CStr(1.15))
cmbPedigreeDog.Items.Add("Working Sheep Dog" & CStr(0.75))
cmbPedigreeDog.Items.Add("Yorkshire Terrier" & CStr(0.88))
cmbPedigreeDog.Items.Add("Other" & CStr(1.22))}
Not the best way, it requires that all your strings in combobox are of the same pattern with same quantity of delimiters.
Example:
If you use instead of "Boxer" & CStr(0.86) something like "Boxer|" & CStr(0.86) (added just a | symbol), then you can split this string back like this.
line = cmbPedigreeDog.Value
price = CDbl(Split(line, "|")(1))
And some explanations:
Each line of text is a container of symbols. Each can be splitted to array with some delimiter. So the line price = CDbl(Split(line, "|")(1)) can be also written like this:
Dim someArray()
line = "Boxer|" & CStr(0.86)
someArray = Split(line, "|")
' after splitting you have two items in array one is "Boxer" and another is "0.86" (which is text)
' if you follow the pattern in each of your combobox options the price always will be the second one
' so you may refer to second item in array, which is number 1
Price = CDbl(someArray(1))
UPDATE
Another way, as per request in comment. Use two columns combobox. To fill your combobox you have to do following
1 - Go to you ComboBox's properties and set the "ColumnCount" values to 2 (see here how to do it).
2 - This is up to you - you may use the binding with prices on a worksheet as like in topic I gave a link in p.1 or change each line on your code to this pattern:
With cmbPedigreeDog
.AddItem ("Boxer")
.column(1, cbx.ListCount - 1) = 0.86
.AddItem ("Chihuahua")
.column(1, cbx.ListCount - 1) = 1.83
' and so on
End With
3 - To retrieve the price of selected item use such code
If cmbPedigreeDog.ListIndex >= 0 Then
price = cmbPedigreeDog.List(cbx.ListIndex, 1)
Else
MsgBox "item is not chosen"
End If
UPDATE 1
This answer is for VBA, not for Visual Basic WinForm application. For this try to look for a solution like "Multi Column ComboBox"
I have a folder ("EDI") [editions] with .txt files inside (01,02,03,04) I have this functional code:
ListBox2.Items.AddRange(File.ReadAllLines(Application.StartupPath & "\Cat\EDI\", "01.txt"))
ListBox2.Items.AddRange(File.ReadAllLines(Application.StartupPath & "\Cat\EDI\", "02.txt"))
ListBox2.Items.AddRange(File.ReadAllLines(Application.StartupPath & "\Cat\EDI\", "03.txt"))
until 68. Each file contains a list songs. But if I try to reduce the code implementing at "For Loop", as:
For i = 1 To 70
ListBox2.Items.AddRange(File.ReadAllLines(Application.StartupPath & "\Cat\EDI\", (i) & ".txt"))
Next
I get an error at (i) & ".txt". Says: "String cannot be converted in coding"
How can i solve?. Some to take in care is the name of the text files are 01.txt,02.txt WITH 2 NUMBERS, also the "for-loop" automatically changes 01 into 1
or better... How can i load all the text lines of all existent text files at folder?
I already have the list of files if it needed, I use this code to get all txt file names into another ListBox:
Dim newroot As String
newroot = (Application.StartupPath & "\Cat\EDI\")
listbox1.items.AddRange(IO.Directory.GetFiles(newroot, "*.txt").
Select(Function(f) IO.Path.GetFileNameWithoutExtension(f)))
The following uses an Interpolated String denoted by the $ preceding the string. This will call .ToString for you on the integer in the { }
Private Sub FileNamesInLoop()
For i As Integer = 1 To 9
ListBox1.Items.Add($"0{i}.txt")
Next
For i2 As Integer = 10 To 68
ListBox1.Items.Add($"{i2}.txt")
Next
End Sub
You can use the Integer.ToString(format) overload and specify number format 00. Doing so will always add a leading zero for numbers < 10, i.e:
5.ToString("00") becomes 05
9.ToString("00") becomes 09
23.ToString("00") becomes 23
Here's how you'd do it:
For i = 1 To 68
ListBox2.Items.AddRange(File.ReadAllLines(Application.StartupPath & "\Cat\EDI\", i.ToString("00") & ".txt"))
Next
Though I highly suggest you switch to Path.Combine() rather than concatenating the paths yourself, as it will ensure everything's done properly:
For i = 1 To 68
ListBox2.Items.AddRange(File.ReadAllLines(Path.Combine(Application.StartupPath, "Cat", "EDI", i.ToString("00") & ".txt")))
Next
I wrote a custom function that parses an HL7 interface message. These are messages sent between healthcare information systems, but basically it's just a long text string, delimited with various characters to indicate different fields, that I paste into a cell in Excel. The function I created searches and counts to find the fields specified in the arguments.
DISCLAIMER: I am new to VBA. I've been teaching myself via online research and trial-and-error over the past 3-4 weeks, so I'm no VBA expert. I'd prefer NOT to use arrays because when I tried that, the code got too complex for me to troubleshoot. So, I'm OK with the code being easy-to-follow, as opposed to being the fastest/most-efficient.
Anyhow, I've got it working pretty well to do what I want, but I'm stuck on adding in logic for an OPTIONAL argument.
So, this is how I WANT it to work:
Formula =KWHL7(A1, "MSH", 8)
NOTE only 3 arguments
Result I Want ADT^A08
Result I Get ADT
NOTE I know I told it to stop at the next instance of "HL7_SUBFIELD_DELIMITER" which is " ^ "
Formula =KWHL7(A1, "MSH", 8,1)
NOTE the optional 4th argument
Result I Want ADT
Formula =KWHL7(A1, "MSH", 8,2)
NOTE the optional 4th argument
Result I Want A08
The contents "value" of cell A1:
<11>MSH|^~\&|OPS|384|RISIC|384|20160923093012||ADT^A08|Q1230569238T1410271390|P|2.3|||*PM_ALLERGY*|||8859/1<13>
EVN||20160923<13>
PID|1||000000808^^^SCH MRN^MRN^SC||ZZTEST^LEANN||20160706|F|||459 CORPORATION ST.^^BEAVER^PA^15009^USA||(724)775-7418^PRN|||S||000000008082^^^SCH Account Number^FIN NBR|||||||0<13>
PV1|1|I|SCH Periop^^^^^^||||08888^Bullian^Leann~08888^Naylor^Daniel|||10|||||||08888^Nguyen-potter^Rose~00187^TEST^STCHRISRES^L^MD^^MD^^SCH Doc Number|1|1287593^^^TEMP FIN^VISITID||||||||||||||||||||384||A|||20160707131900<13>
PV2|||PA^<13>
OBX|1||Dosing Weight^WEIGHT||5|kg<13>
OBX|2||Height^HEIGHT||25|cm<13>
AL1|1|Drug|d00308^morphine^Multum Drug||66382015<13>
ZAL|||16655315|16655315||Active|66382015^Anaphylaxis^673967||||20160923093008|^Naylor^Daniel|0<13>
AL1|3|Drug|d00012^codeine^Multum Drug||103576018<13>
ZAL|||16655323|16655307||Active|103576018^Diarrhea^673967||||20160923093008|^Naylor^Daniel|0<13>
<28><13>
My VBA code (sorry for all the comments, I'm just learning!):
Public Function KWHL7(KW_Cell_With_HL7_Message As Variant, KW_HL7_Segment_Name As String, KW_HL7_Field_Number As Integer)
'KW_Cell_With_HL7_Message = KW_Cell_With_HL7_Message.Value
'KW_Cell_With_HL7_Message = ActiveCell.Value
'KW_HL7_Segment_Name = "PID"
'KW_HL7_Field_Number = 18
Const HL7_SEGMENT_DELIMITER = vbLf 'using "<13>" did not work due to carriage return
Const HL7_FIELD_DELIMITER = "|" ' Pipe means next field
Const HL7_SUBFIELD_DELIMITER = "^"
'Various carriage returns and line breaks: vbLf, vbCr, vbCrLf, vbNewLine, Chr(10), Chr(13)
KWSegmentStringToSearchFor = HL7_SEGMENT_DELIMITER & KW_HL7_Segment_Name 'Using the segment delimiter ("<13>" or "vbLf" / carriage return) before segment name implies that the segment / line STARTS with this text
KWSegmentCharacterPosition = InStr(1, KW_Cell_With_HL7_Message, KWSegmentStringToSearchFor)
'** FOR TESTING ** MsgBox ("Segment Character Position: " & KWSegmentCharacterPosition & ", 5 Characters starting there = " & Mid(KW_Cell_With_HL7_Message, KWSegmentCharacterPosition, 5))
'Now we have the character position of the start of the proper SEGMENT / line
'Now we have to find the Proper Field in that segment
'So we'll use this position + the length of the end of the Segment Delimiter as the start
'***WARNING***: Still must add logic to make sure we stop if we encounter another Segment Delimiter
KWFieldCharacterPosition = KWSegmentCharacterPosition + Len(HL7_SEGMENT_DELIMITER) 'instead of starting at character 0, start at the beginning of the segment found previously
' ** FOR TESTING ** MsgBox ("Length of Segment Delimiter = " & Len(HL7_SEGMENT_DELIMITER))
' ** FOR TESTING ** MsgBox ("Field Character Position: " & KWFieldCharacterPosition & ", 5 Characters starting there = " & Mid(KW_Cell_With_HL7_Message, KWFieldCharacterPosition, 5))
For J = 1 To KW_HL7_Field_Number
KWFieldCharacterPosition = InStr(KWFieldCharacterPosition + 1, KW_Cell_With_HL7_Message, HL7_FIELD_DELIMITER)
If KWFieldCharacterPosition = 0 Then Exit For
Next
' ** FOR TESTING ** MsgBox ("Field Character Position: " & KWFieldCharacterPosition & ", 5 Characters starting there = " & Mid(KW_Cell_With_HL7_Message, KWFieldCharacterPosition, 5))
'Determine the number of characters to return after the start position
'Want to pull text UNTIL the next Segment Delimiter or Field Delimiter or Subfield Delimiter
'Find the position of the next Segment Delimiter or Field Delimiter or Subfield Delimiter
'Since the InStr function does not accept multiple substrings to search for, and does not allow OR statements inside...
Next_HL7_Segment_Delimiter = InStr(KWFieldCharacterPosition + 1, KW_Cell_With_HL7_Message, HL7_SEGMENT_DELIMITER)
Next_HL7_Field_Delimiter = InStr(KWFieldCharacterPosition + 1, KW_Cell_With_HL7_Message, HL7_FIELD_DELIMITER)
Next_HL7_Subfield_Delimiter = InStr(KWFieldCharacterPosition + 1, KW_Cell_With_HL7_Message, HL7_SUBFIELD_DELIMITER)
'Added logic to handle issue where the next delimiter was not found, making result 0, making it the lowest value in the next lines of code
If Next_HL7_Segment_Delimiter = 0 Then Next_HL7_Segment_Delimiter = 99999
If Next_HL7_Field_Delimiter = 0 Then Next_HL7_Field_Delimiter = 99999
If Next_HL7_Subfield_Delimiter = 0 Then Next_HL7_Subfield_Delimiter = 99999
'Set the Last Character Position to whichever Next Delimiter is the lowest / minimum number - Segment or Field or Subfield
KWLastCharacterPosition = WorksheetFunction.Min(Next_HL7_Segment_Delimiter, Next_HL7_Field_Delimiter, Next_HL7_Subfield_Delimiter)
' ** FOR TESTING ** MsgBox ("Last Character Position: " & KWLastCharacterPosition & ", 5 Characters starting there = " & Mid(KW_Cell_With_HL7_Message, KWLastCharacterPosition, 5))
'Determine the number of characters to return in the MID function by subtracting the first character position from the last character position
KWNumberOfCharactersToReturn = KWLastCharacterPosition - KWFieldCharacterPosition - 1
' ** FOR TESTING ** MsgBox ("Number of characters to return: " & KWNumberOfCharactersToReturn)
KWResult = Mid(KW_Cell_With_HL7_Message, KWFieldCharacterPosition + 1, KWNumberOfCharactersToReturn)
'MsgBox ("Result: Segment " & KW_HL7_Segment_Name & ":" & KW_HL7_Field_Number & " is " & KWResult)
KWHL7 = KWResult
End Function
The problem I had with using the split function was that it put everything into arrays. And since I needed to search FIRST for the KWSegmentStringToSearchFor (i.e. "MSH" or "PV1"), before couting the pipe (|) characters, I would need the array to have separate nested arrays and it got way too confusing for me.
So I abandoned the split function, and my initial plans to use arrays, and just wrote everything to find things sequentially. So it searches for the KWSegmentStringToSearchFor (i.e. "MSH" or "PV1") with InStr() and then counts the pipe (|) characters from there to determine which number field to return.
Since the strings are of variable length, but delimited with special characters, next I have to determine how many characters to return with the MID function. So I search for the next delimiter FROM THERE / using the field I found as the starting point and call that the end of my field.
The issue:
The logic considers ANY of the 3 possible delimiters the end of the field.
If I take that out, the code wouldn't know where the end of the string is.
Even if I add some sort of IF statement that IF the optional 4th argument exists (which I'm not sure how to do yet), THEN ignore the ^ as a delimiter... that would always return the full field (ADT^A08). It wouldn't return just the sub-field / component I want.
Thanks!
A simple answer, would be to split on the LineFeed then it may need tweaking would be, split(range("a1").value,"|")(intFieldNumber)
i.e.
split("11>MSH|^~\&|OPS|384|RISIC|384|20160923093012||ADT^A08|Q1230569238T1410271390","|")(8)
gives the result ADT^A08
I've tried doing some searchs but for the life of me I don't seem to be able to find the answer, or a suggested solution that works. Its probably my understanding, but hopefully asking my own question will give me an answer that works :o)
I have a Windows Form Application which consists of one item, ListView1
This ListView has items added to it from a file via a Drag / Drop which is done on the main UI Thread, no background worker, it consists of around 1500 rows.
I'm trying to get a background worker now to read this ListView, but I'm getting a Cross Threading error as ListView1 was not created on the same thread.
The error comes on the simplest of pieces of code, but I don't seem to be able to think of a way around it or implementing an invoke etc.
For i = 0 To Me.ListView1.Items.Count - 1
ValueStatement = ValueStatement & "(" & Me.ListView1.Items(i).SubItems(0).Text
If i = Me.ListView1.Items.Count - 1 Or counter = 500 Then
CommaTerminate = ";"
Else
CommaTerminate = ","
End If
For y = 0 To Me.ListView1.Columns.Count - 1
ValueStatement = ValueStatement & "'" & Me.ListView1.Items(i).SubItems(y).Text & "'"
If y = Me.ListView1.Columns.Count - 1 Then
ValueStatement = ValueStatement & ")"
Else
ValueStatement = ValueStatement & ","
End If
Next
ValueStatement = ValueStatement & CommaTerminate & vbNewLine
If counter = 500 Then
SQLStatement = "INSERT INTO RAW_CLI_DATA_" & GlobalVariables.CDR_Company & " VALUES " & vbNewLine & ValueStatement
GenericDatabaseRequest(SQLStatement, "Loading RAW table with data..")
counter = 0
ValueStatement = ""
End If
counter = counter + 1
Next
The error comes on the line ValueStatement = ValueStatement & "(" & Me.ListView1.Items(i).SubItems(0).Text
Thanks for any help!
It sounds like you went down the wrong road early on. The ListView is supremely illsuited for database ops:
Everything is contained as String which means somewhere you will have code to convert it to other types
It does not support databinding which means you have to manually create rows...
... then later iterate them to fish the data back out.
A DataGridView and DataTable would be simpler: When the user enters data into the control and it would be stored in the table and as the proper type. Setting the DataTable as the Datasource, the DataGridView would create the display (rows and columns) for you.
Some of the time it takes will be consumed by SQLite to perform the INSERT, but it also looks like you are spending a lot of time iterating and concatenating SQL. It's usually better to work with the data than the user's View of it anyway, so extract and pass the data to the worker.
First, suck the data out of the ListView into a String()() container:
Dim data = lv.Items.
Cast(Of ListViewItem).
Select(Function(s) New String() {s.SubItems(0).Text,
s.SubItems(1).Text,
s.SubItems(2).Text}).
ToArray()
Then pass it to the BackGroundWorker:
bgw.RunWorkerAsync(data)
The DoWork Event:
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
' unbox the data
Dim dataToInsert = CType(e.Argument, String()())
For n As Int32 = 0 To 2
Console.WriteLine("[{0}], [{1}], [{2}]", dataToInsert(n)(0),
dataToInsert(n)(1),
dataToInsert(n)(2))
Next
End Sub
Results:
[Patient Tempest], [Lorem ipsum dolor sit], [Swordfish]
[Sour Priestess], [hendrerit nibh tempor], [Perch]
[Frozen Justice], [Interdum ex felis], [Swordfish]
It correctly prints the random data I put into the LV.
This will allow you to process the ListView Data in the BackGroundWorker but it wont really save any time, it just keeps the UI unlocked. The real problem is elsewhere, probably in the DB ops.
Using VBScript, I create a recordset from a SQL query through and ADO connection object. I need to be able to write the field names and the largest field length to a text file, essentially as a two dimensional array, in the format of FieldName|FieldLength with a carriage return delimiter, example:
Matter Number|x(13)
Description|x(92)
Due Date|x(10)
Whilst I am able to loop through the Columns and write out the field names, I cannot solve the issue of Field Length. Code as follows:
Set objColNames = CreateObject("Scripting.FileSystemObject").OpenTextFile(LF14,2,true)
For i=0 To LF06 -1
objColNames.Write(Recordset.Fields(i).Name & "|x(" & Recordset.Fields(i).ActualSize & ")" & vbCrLf)
Next
in this instance it only writes the current selected Field Length.
If I understand the question correctly (I'm not certain I do)....
If you change your SQL statement you only need to return one record.
Select Max(Len([Matter Number])) as [Matter Number],
Max(Len([Description])) As Description, Max(Len([Due Date])) As [Due Date] FROM TableName
This will return the maximum length of each field. Then construct your output from there.
To get an extrema item (largest, smallest, ...) of a collection you need a loop over all elements that checks the current element's value against the known 'extrema so far':
>> a = Array(1, 3, 2)
>> x = a(0)
>> For i = 1 To UBound(a)
>> If a(i) > x Then
>> x = a(i)
>> End If
>> Next
>> WScript.Echo x
>>
3
After a little more research and testing I solved the issue by creating a dictionary based on the recordset field (column) count, then iterating through each item and evaluating the length of each field:
Dim Connection
Dim Recordset
Set Connection = CreateObject("ADODB.Connection")
Set Recordset = CreateObject("ADODB.Recordset")
Connection.Open LF08
Recordset.Open LF05,Connection
LF06=Recordset.Fields.Count
Set d = CreateObject("Scripting.Dictionary")
Set objColNames = CreateObject("Scripting.FileSystemObject").OpenTextFile(LF14,2,true)
For i=0 to LF06 -1
d.Add i, 0
Next
Dim aTable1Values
aTable1Values=Recordset.GetRows()
Set objFileToWrite = CreateObject("Scripting.FileSystemObject").OpenTextFile(LF07,2,true)
Dim iRowLoop, iColLoop
For iRowLoop = 0 to UBound(aTable1Values, 2)
For iColLoop = 0 to UBound(aTable1Values, 1)
If d.item(iColLoop) < Len(aTable1Values(iColLoop, iRowLoop)) Then
d.item(iColLoop) = Len(aTable1Values(iColLoop, iRowLoop))
End If
If IsNull(aTable1Values(iColLoop, iRowLoop)) Then
objFileToWrite.Write("")
Else
objFileToWrite.Write(aTable1Values(iColLoop, iRowLoop))
End If
If iColLoop <> UBound(aTable1Values, 1) Then
objFileToWrite.Write("|")
End If
next 'iColLoop
objFileToWrite.Write(vbCrLf)
Next 'iRowLoop
For i=0 to LF06 -1
d.item(i) = d.item(i) + 3
objColNames.Write(Recordset.Fields(i).Name & "|x(" & d.item(i) & ")" & vbCrLf)
Next
I then have two text files, one with the field names and lengths, the other with the query results. Using this I can then create a two dimensional array in the CMS (VisualFiles) from the results.