vb.net list confusion - vb.net

I am looping through a list for a spellchecker in vb.net (using vs 2010). I want to go through a wrongly spelled word list. Each time the code picks the index that's one higher than the index of the last checked word.
In my version of notquiteVB/Pythonese I think it would translate something like:
(start loop)
dim i as Integer = 0
dim word as String
word = words_to_check_at_spellcheck.Item(0 + i)
i = i+1
(end loop)
But this doesn't work at all...when it gets to the last item in the list and reaches 'word = ' it throws the error of 'out of range -- must be less than the size of the collection'.
How do you get the last item in a list? Maybe lists aren't what VB uses for this kind of thing?

If you're collection of misspelled words is named mispelled:
For Each word As String In mispelled
'Do something
Next

Related

How can I extract and use the value from a Word rich text content control in Outlook VBA?

I want to print a number of pages which depends on the number filled in in a Word content control. I tried referring to the object with this:
Set objCC = xFileName.SelectContentControlsByTag("q_179").Item(1)
Total = objCC
Counter = 0
Do
Counter = Counter + 1
xFolderItem.InvokeVerbEx ("print")
Loop While Counter < Total
Does anyone know a solution or spot something I'm missing in the code? If you need more info lmk!
Click here to view full code
The problem is that xFileName.SelectContentControlsByTag("q_179").Item(1) returns a ContentControl object, and a ContentControl object is not a number that can be compared in an expression such as Counter < Total
In this case the way to get the number is to get the ContentControl's Range.Text and convert it to a number (and you might also need to verify that it is a number).
e.g.assuming you know the number is an Integer you could use
Dim ccValue As Integer
ccValue = CInt(xFileName.SelectContentControlsByTag("q_179").Item(1).Range.Text)
.
.
Loop While Counter < ccValue
Because VBA makes a number of assumptions, if you know the value is Numeric you could probably 'get away' with something more like
Dim ccValue As Integer
ccValue = xFileName.SelectContentControlsByTag("q_179").Item(1).Range
.
.
Loop While Counter < ccValue
You may be wondering why it's valid to use .Range rather than .Range.Text but not OK to use (in effect) objCC.Range. That's because .Text is the "Default Member" of the Range object, so when you use .Range without specifying a member, VBA returns the value of the default member. But I think a lot of people would say it's not a very good practice and it's better to specify the member.
To explore Default Members in more detail, open the View->Object Broswer window in Word's VB Editor, and search for the object/class, e.g. Range. Select it, and the relevant Class should be selected in the list of Classes (to the bottom left, probably). Then look through the list of members of the class. If there is a Default Member, its name should be bolded.
You may also be wondering why it's valid to omit the CInt(). It's because VB can "coerce" a string type into being a numeric type, when it contains a number at any rate. But it's usually better to specify an explicit conversion (especially because the result of a conversion can depend on Regional locale settings in e.g. Windows).
I'm now using this, it didn't work before because the content control is inside a table. Thanks #jonsson for your answer!
counter = 0
ccvalue = CInt(objWordDocument.SelectContentControlsByTag("q_179").Item(1).Range.Text)
For Each Table In objWordDocument.Tables
Do
counter = counter + 1
xFolderItem.InvokeVerbEx ("print")
Excel.Application.Wait (Now + TimeValue("00:00:01"))
Loop While counter < ccvalue
Next

How do i split a massive string into smaller parts of itself?

Hey so i have a school project in which i need to split a massive word into smaller words.
This is the massive sequence of letters :
'GLSDGEWQQVLNVWGKVEADIAGHGQEVLIRLFTGHPETLEKFDKFKHLKTEAEMKASEDLKKHGTVVLTALGGILKKKEGH
HEAELKPLAQSHATKHKIPIKYLEFISDAIIHVLHSKHRPGDFGADAQGAMTKALELFRNDIAAKYKELGFQG'
and then i need to split it into other smaller separate parts of itself which would look like this :
'GLSDGEWQQVLNVWGK'
'VEADIAGHGQEVLIR'
'LFTGHPETLEK'
'FDK'
'FK'
'HLK'
'TEAEMK'
'ASEDLK'
'K'
'HGTVVLTALGGILK'
'K'
'K'
'EGHHEAELKPLAQSHATK'
'HK'
'IPIK'
'YLEFISDAIIHVLHSK'
'HRPGDFGADAQGAMTK'
'ALELFR'
'NDIAAK'
'YK'
'ELGFQG'
i have no idea how to start on this if you could help pls and thanks
Different digestion enzymes cut at different positions within a protein sequence; the most commonly used one is trypsin. It follows the following rules: 1) Cuts the sequence after an arginine (R) 2) Cuts the sequence after a lysine (K) 3) Does not cut if lysine (K) or arginine (R) is followed by proline (P).
Okay, hooray, rules! Let's turn this into pseudo-code to describe the same algorithm in a half-way state between the original prose and code. (While a Regex.Split approach would still work, this might be a good time to explore some more fundamentals.)
let the list of words be an empty array
let current word be the empty string
for each letter in the input:
if the letter is R or K and the next letter is NOT P then:
add the letter to the current word
save the current word to the list of words
reset the current word to the empty string
otherwise:
add the letter to the current word
if after the loop the current word is not the empty string then:
add the current word to the list of words
Then let's see how some of these translate. This is incomplete and quite likely contains minor errors1 beyond that which has been called out in comments.
Dim words As New List(Of String)
Dim word = ""
' A basic loop works suitably for string input and it can also be
' modified for a Stream that supports a Peek of the next character.
For i As Integer = 0 To input.Length - 1
Dim letter = input(i)
' TODO/FIX: input(i+1) can access an element off the string. Reader exercise.
If (letter = "R"C OrElse letter = "K"C) AndAlso Not input(i+1) = "P"C
' StringBuilder is more efficient for larger strings
Set word = word & letter
words.Add(word) ' or perhaps just WriteLine the word somewhere?
Set word = ""
Else
Set word = word & letter
End If
Next
' TODO/FIX: consider when last word is not added to words list yet
1As I use C# (and not VB.NET) the above code comes warranty Free. Here are some quick reference links I used to 'stitch' this together:
https://www.dotnetperls.com/loop-string-vbnet
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/operators-and-expressions/concatenation-operators
https://www.dotnetperls.com/list-vbnet
https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/dim-statement
How do you declare a Char literal in Visual Basic .NET?

Streamwriter: write two listboxes on the same row

I am trying to write, in order to export on txt file, information in two listbox with the same number of rows. I have to export them with the following format: Listbox1, Listbox2. In order to do this, I've tried to use the following code:
Using writer = New StreamWriter(SaveFileDialog1.FileName)
For Each o As Object In Form3.ListBox1.Items And Form3.ListBox2.Items
writer.WriteLine(o)
Next
End Using
I'm receiving the following error:
BC30452 Operator 'And' is not defined for types 'ListBox.ObjectCollection' and 'ListBox.ObjectCollection'.
I've also tried to perform three For Each loops, the first for the LB1, the second for the commas and the third for LB2, but I'm having it exported with content on single lines. How could I solve this?
If you use Enumerable.Zip, as suggested in another answer, then you can make the code more succinct by doing away with the explicit loop:
File.WriteAllLines(SaveFileDialog1.FileName,
Form3.ListBox1.
Items.
Cast(Of Object).
Zip(Form3.ListBox2.
Items.
Cast(Of Object),
Function(x1, x2) $"{x1}, {x2}"))
If you didn't use Zip then you can use a loop this way:
Dim items1 = Form3.ListBox1.Items
Dim items2 = Form3.ListBox2.Items
Using writer = New StreamWriter(SaveFileDialog1.FileName)
For i = 0 To Math.Min(items1.Count, items2.Count)
writer.WriteLine($"{items1(i)}, {items2(i)}")
Next
End Using
The Math.Min part is just in case there are different numbers of items in each ListBox. If you know there aren't then you can do away with that and just use one Count. If there might be different counts but you want to output all items then the code would become slightly more complex to handle that.
As the error message says, the syntax you attempted is simply not valid. There's no feature in VB.NET that does that sort of thing.
However, the .NET Framework API does provide a means for something similar, which would probably work in your case. See Enumerable.Zip(). You can use it like this:
Using writer = New StreamWriter(SaveFileDialog1.FileName)
For Each o As String In Form3.ListBox1.Items.Cast(Of Object).Zip(Form3.ListBox2.Items.Cast(Of Object), Function(x1, x2) x1 & ", " & x2)
writer.WriteLine(o)
Next
End Using
Since you said that both list boxes have the same number of items we can use the number of items in the first listbox less one (indexes start at zero) in a For loop.
I used a StringBuilder so the code does not have to throw away and create a new string on each iteration.
I used an interpolated string indicate by the $ preceding the string. This means I can insert variables in braces, right along with literals.
Call .ToString on the StringBuilder to write to the text file.
Private Sub SaveListBoxes()
Dim sb As New StringBuilder
For i = 0 To ListBox1.Items.Count - 1
sb.AppendLine($"{ListBox1.Items(i)}, {ListBox2.Items(i)}")
Next
File.WriteAllText("C:\Users\xxx\Desktop\ListBoxText.txt", sb.ToString)
End Sub

Counting words in Word document, including footnores

I periodically receive long documents that include footnotes and am trying to find a way using VBA to count the number of words on each page, including footnotes. It doesn't matter if a footnote spills over onto the next page, I just the word count including footnotes that are anchored on the page.
I have a macro that correctly counts the number of words in the body of the text, using the command:
WordCount = ActiveDocument.Range(Start:=pos1, End:=pos2).ComputeStatistics(wdStatisticWords)
The variables pos1 and pos2 have been set to the first and last characters of the page being counted.
However, when I add the True parameter to ComputeStatistics(wdStatisticWords, True), to IncludeFootnotesAndEndnotes, as in:
WordCount = ActiveDocument.Range(Start:=pos1, End:=pos2).ComputeStatistics(wdStatisticWords, True)
it doesn't work, giving an error that there are too many parameters. It appears that when using a Range, the IncludeFootnotesAndEndnotes parameter is not available.
How do you count the words within footnotes contained in a range?
I think what you will need to do is iterate into each of the StoryRanges and update a counter. Here is a small example that should serve as an example, however, you will likely need to tweak it for your specific case (review my note about the enum for StoryRanges)
Here's the code:
Public Sub Count_All_Words()
Dim Story_Ranges As Variant: Set Story_Ranges = ActiveDocument.StoryRanges
Dim Story_Range As Object
Dim WordCount As Long
'Loop through each story range and only include the footer and Main story to the word count
For Each Story_Range In Story_Ranges
'You may need to check additional types, lookup the enumerations for StoryType here:
'https://msdn.microsoft.com/en-us/library/bb238219(v=office.12).aspx
If Story_Range.StoryType = wdMainTextStory Or Story_Range.StoryType = wdFootnoteSeparatorStory Then
'Add to the word count
WordCount = WordCount + Story_Range.ComputeStatistics(wdStatisticWords)
End If
Next
Debug.Print "The word count is: " & WordCount
End Sub

How should I handle filling multiple textboxes when I don't know how many of them will have data?

I'm writing an application in VB in which I need to show the user some information which will be copy and pasted into another application however limitations of the other application mean that the string needs to be split into chunks no larger than 55 characters (it's just written notes). I thought the neatest way to do this was to have several textboxes each with a 'copy to clipboard' button to make it convenient for the user.
The code I have is:
Dim invdesc As List(Of String) = Split(splitstring, 55)
txtinvDesc1.Text = invdesc(0)
txtinvDesc2.Text = invdesc(1)
txtinvDesc3.Text = invdesc(2)
...
Split uses a regular expression to return a list of several lines without breaking up words and most of the time this will return a maximum of seven results but occasionally six (my original string max length is 330) and often fewer so my original idea to fill out any strings shorter than 330 with trailing spaces won't work as it's still possible I will either miss text or call a result that isn't there.
Ideally I would just do some kind of loop that only inputs to txtinvDesc(x) while there is data available and ignores the rest (or hides them) but I don't know any way to refer to a textbox other than explicitly or how to put them in any kind of list/array.
So it's a bit of an open question in "how best can I handle this requirement?"
You can create a collection (e.g., Array or List) of TextBox like with any other type/class (as you are doing with String in your code). Sample:
Dim allTextBoxes As New List(Of TextBox)
allTextBoxes.Add(txtinvDesc1)
allTextBoxes.Add(txtinvDesc2)
allTextBoxes.Add(txtinvDesc3)
Alternatively, you might iterate through all the controls in the main form by checking its type (a textbox or not). In that case you would have to set a relationship between the given name of the textbox and the data list index, via other collection for example:
Dim mappingList As New List(Of String)
mappingList.Add("txtinvDesc1")
mappingList.Add("txtinvDesc2")
mappingList.Add("txtinvDesc3")
For Each ctr As Control In Me.Controls
If (TypeOf ctr Is TextBox AndAlso mappingList.Contains(ctr.Name)) Then
ctr.Text = invdesc(mappingList.IndexOf(ctr.Name))
End If
Next
--- CLARIFICATION (not as evident as I thought)
The proposed for each loop relies on a mapping approach, that is, it relates each element in invdesc with the corresponding TextBox name. By definition, both arrays HAVE TO have the same number of elements (otherwise the mapping system wouldn't have made any sense). This is the most efficient and overall-applicable alternative; if the names of the textboxes and invdesc have elements in common (e.g., the numbers), you might just compare the names. BUT WHEN MAPPING YOU HAVE TO ACCOUNT FOR ALL THE ELEMENTS (if there is no associated TextBox to a given item, let the value blank; but all the items have to be accounted).
If you want to index the tbs:
Private TBs as New List (of TextBox)
Early on (after FormLoad) maybe in a FormSetup:
TBs.Add(txtinvDesc1)
TBs.Add(txtinvDesc2)
TBs.Add(txtinvDesc3)
...
Then:
Dim invdesc As List(Of String) = Split(splitstring, 55)
For n As Integer = 0 To invdesc.Count-1
TBs(n).Text = invdesc(n)
Next
' handle the varying 7th TB:
For n As Integer = invdesc.Count-1 To TBs.Count - 1
TBs(n).Enabled = False
TBs(n).Text =""
Next
Or a For/Each:
Dim ndx As Integer = 0
For Each tb As TextBox In TBs
tb.Text = invdesc(ndx)
ndx += 1 ' thanks varo!
Next
Then hide/disable or at least clear the text from any empty ones.
If it turns out there are always 6 you really only need an if statement:
txtinvDesc1.Text = invdesc(0)
txtinvDesc2.Text = invdesc(1)
txtinvDesc3.Text = invdesc(2)
...
If incDesc.Count-1 = 6 Then
txtinvDesc7.Text = invdesc(6)
Else
txtinvDesc7.Enabled= False
txtinvDesc7.Text = ""
End If
I would change the TB names to start at txtinvDesc0.Text to avoid getting confused (as I may have)
Use multiline textbox and in OnKeyPress event force 55 chars per line. You can find subclassed TextBox with that feature in this SO answer :
https://stackoverflow.com/a/17082189/351383